2009-12-30 10:20:44 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
2012-05-25 15:20:46 +00:00
# Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>)
2009-12-30 10:20:44 +00:00
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
2012-12-17 15:46:28 +00:00
from openerp . addons . base_status . base_stage import base_stage
2010-01-13 13:34:09 +00:00
import crm
2012-05-25 15:20:46 +00:00
from datetime import datetime
2012-12-06 14:56:32 +00:00
from openerp . osv import fields , osv
2010-04-05 09:50:29 +00:00
import time
2012-12-06 14:56:32 +00:00
from openerp import tools
from openerp . tools . translate import _
from openerp . tools import html2plaintext
2010-07-08 14:03:52 +00:00
2013-02-19 08:42:55 +00:00
from openerp . addons . base . res . res_partner import format_address
2010-07-08 14:03:52 +00:00
2012-12-04 13:30:00 +00:00
CRM_LEAD_FIELDS_TO_MERGE = [ ' name ' ,
' partner_id ' ,
' channel_id ' ,
' company_id ' ,
' country_id ' ,
' section_id ' ,
' state_id ' ,
2013-02-06 11:06:56 +00:00
' stage_id ' ,
2012-12-04 13:30:00 +00:00
' type_id ' ,
' user_id ' ,
' title ' ,
' city ' ,
' contact_name ' ,
' description ' ,
' email ' ,
' fax ' ,
' mobile ' ,
' partner_name ' ,
' phone ' ,
' probability ' ,
' planned_revenue ' ,
' street ' ,
' street2 ' ,
' zip ' ,
' create_date ' ,
' date_action_last ' ,
' date_action_next ' ,
' email_from ' ,
' email_cc ' ,
' partner_name ' ]
2010-07-08 14:03:52 +00:00
CRM_LEAD_PENDING_STATES = (
crm . AVAILABLE_STATES [ 2 ] [ 0 ] , # Cancelled
crm . AVAILABLE_STATES [ 3 ] [ 0 ] , # Done
crm . AVAILABLE_STATES [ 4 ] [ 0 ] , # Pending
)
2012-09-13 18:19:52 +00:00
class crm_lead ( base_stage , format_address , osv . osv ) :
2010-03-23 14:12:01 +00:00
""" CRM Lead Case """
2009-12-30 10:20:44 +00:00
_name = " crm.lead "
2010-12-20 11:38:20 +00:00
_description = " Lead/Opportunity "
2011-08-25 04:10:37 +00:00
_order = " priority,date_action,id desc "
2012-12-18 12:56:34 +00:00
_inherit = [ ' mail.thread ' , ' ir.needaction_mixin ' ]
_track = {
2012-12-18 23:49:07 +00:00
' state ' : {
2012-12-19 16:40:48 +00:00
' crm.mt_lead_create ' : lambda self , cr , uid , obj , ctx = None : obj [ ' state ' ] == ' new ' ,
2012-12-18 23:49:07 +00:00
' crm.mt_lead_won ' : lambda self , cr , uid , obj , ctx = None : obj [ ' state ' ] == ' done ' ,
' crm.mt_lead_lost ' : lambda self , cr , uid , obj , ctx = None : obj [ ' state ' ] == ' cancel ' ,
} ,
2012-12-18 12:56:34 +00:00
' stage_id ' : {
2012-12-19 16:40:48 +00:00
' crm.mt_lead_stage ' : lambda self , cr , uid , obj , ctx = None : obj [ ' state ' ] not in [ ' new ' , ' cancel ' , ' done ' ] ,
2012-12-18 12:56:34 +00:00
} ,
}
2011-08-25 04:10:37 +00:00
2013-02-05 16:10:02 +00:00
def dynamic_help ( self , cr , uid , help , context = None ) :
2013-02-06 09:44:14 +00:00
if context . get ( ' default_type ' , None ) == ' lead ' :
context [ ' dynamic_help_model ' ] = ' crm.case.section '
context [ ' dynamic_help_id ' ] = context . get ( ' default_section_id ' , None )
context [ ' dynamic_help_documents ' ] = _ ( " leads " )
return super ( crm_lead , self ) . dynamic_help ( cr , uid , help , context = context )
2013-02-05 16:10:02 +00:00
2013-02-06 11:57:36 +00:00
def onchange_user_id ( self , cr , uid , ids , section_id , user_id , context = None ) :
if user_id :
section_ids = self . pool . get ( ' crm.case.section ' ) . search ( cr , uid , [ ' | ' , ( ' user_id ' , ' = ' , user_id ) , ( ' member_ids ' , ' = ' , user_id ) ] , context = context )
if section_ids and ( not section_id or section_id not in section_ids ) :
section_id = section_ids [ 0 ]
return { ' value ' : { ' section_id ' : section_id } }
2012-12-28 12:18:58 +00:00
def create ( self , cr , uid , vals , context = None ) :
2012-12-28 14:39:12 +00:00
if context is None :
context = { }
2013-01-15 10:03:53 +00:00
if not vals . get ( ' stage_id ' ) :
2012-12-28 12:18:58 +00:00
ctx = context . copy ( )
2013-01-15 10:03:53 +00:00
if vals . get ( ' section_id ' ) :
ctx [ ' default_section_id ' ] = vals [ ' section_id ' ]
if vals . get ( ' type ' ) :
ctx [ ' default_type ' ] = vals [ ' type ' ]
2012-12-28 12:18:58 +00:00
vals [ ' stage_id ' ] = self . _get_default_stage_id ( cr , uid , context = ctx )
return super ( crm_lead , self ) . create ( cr , uid , vals , context = context )
2012-05-24 14:26:16 +00:00
def _get_default_section_id ( self , cr , uid , context = None ) :
""" Gives default section by checking if present in the context """
2013-01-03 15:06:53 +00:00
return self . _resolve_section_id_from_context ( cr , uid , context = context ) or False
2012-05-24 14:26:16 +00:00
2012-05-25 11:51:02 +00:00
def _get_default_stage_id ( self , cr , uid , context = None ) :
""" Gives default stage_id """
section_id = self . _get_default_section_id ( cr , uid , context = context )
2012-10-31 12:17:20 +00:00
return self . stage_find ( cr , uid , [ ] , section_id , [ ( ' state ' , ' = ' , ' draft ' ) ] , context = context )
2012-05-25 11:51:02 +00:00
2012-05-22 14:11:27 +00:00
def _resolve_section_id_from_context ( self , cr , uid , context = None ) :
""" Returns ID of section based on the value of ' section_id '
2012-05-24 14:26:16 +00:00
context key , or None if it cannot be resolved to a single
Sales Team .
2012-05-22 14:11:27 +00:00
"""
if context is None :
context = { }
2012-05-24 14:26:16 +00:00
if type ( context . get ( ' default_section_id ' ) ) in ( int , long ) :
return context . get ( ' default_section_id ' )
if isinstance ( context . get ( ' default_section_id ' ) , basestring ) :
section_name = context [ ' default_section_id ' ]
2012-05-22 14:29:54 +00:00
section_ids = self . pool . get ( ' crm.case.section ' ) . name_search ( cr , uid , name = section_name , context = context )
if len ( section_ids ) == 1 :
2012-05-24 14:26:16 +00:00
return int ( section_ids [ 0 ] [ 0 ] )
2012-05-25 15:20:46 +00:00
return None
def _resolve_type_from_context ( self , cr , uid , context = None ) :
""" Returns the type (lead or opportunity) from the type context
key . Returns None if it cannot be resolved .
"""
if context is None :
context = { }
return context . get ( ' default_type ' )
2011-08-25 04:10:37 +00:00
2011-11-23 13:31:32 +00:00
def _read_group_stage_ids ( self , cr , uid , ids , domain , read_group_order = None , access_rights_uid = None , context = None ) :
access_rights_uid = access_rights_uid or uid
2011-11-13 12:07:15 +00:00
stage_obj = self . pool . get ( ' crm.case.stage ' )
2011-11-21 16:49:40 +00:00
order = stage_obj . _order
2012-05-22 15:08:11 +00:00
# lame hack to allow reverting search, should just work in the trivial case
2011-11-21 16:49:40 +00:00
if read_group_order == ' stage_id desc ' :
order = " %s desc " % order
2012-05-22 15:08:11 +00:00
# retrieve section_id from the context and write the domain
2012-05-31 13:16:26 +00:00
# - ('id', 'in', 'ids'): add columns that should be present
# - OR ('case_default', '=', True), ('fold', '=', False): add default columns that are not folded
# - OR ('section_ids', '=', section_id), ('fold', '=', False) if section_id: add section columns that are not folded
2012-05-22 14:11:27 +00:00
search_domain = [ ]
2012-05-22 15:08:11 +00:00
section_id = self . _resolve_section_id_from_context ( cr , uid , context = context )
2012-05-22 14:11:27 +00:00
if section_id :
2012-09-06 16:18:12 +00:00
search_domain + = [ ' | ' , ( ' section_ids ' , ' = ' , section_id ) ]
2012-12-16 15:58:43 +00:00
search_domain + = [ ( ' id ' , ' in ' , ids ) ]
else :
search_domain + = [ ' | ' , ( ' id ' , ' in ' , ids ) , ( ' case_default ' , ' = ' , True ) ]
2012-05-31 13:16:26 +00:00
# retrieve type from the context (if set: choose 'type' or 'both')
type = self . _resolve_type_from_context ( cr , uid , context = context )
2012-05-25 15:20:46 +00:00
if type :
search_domain + = [ ' | ' , ( ' type ' , ' = ' , type ) , ( ' type ' , ' = ' , ' both ' ) ]
2012-05-22 14:11:27 +00:00
# perform search
stage_ids = stage_obj . _search ( cr , uid , search_domain , order = order , access_rights_uid = access_rights_uid , context = context )
2011-11-23 13:31:32 +00:00
result = stage_obj . name_get ( cr , access_rights_uid , stage_ids , context = context )
2011-11-21 16:49:40 +00:00
# restore order of the search
result . sort ( lambda x , y : cmp ( stage_ids . index ( x [ 0 ] ) , stage_ids . index ( y [ 0 ] ) ) )
2012-09-06 15:23:03 +00:00
fold = { }
for stage in stage_obj . browse ( cr , access_rights_uid , stage_ids , context = context ) :
fold [ stage . id ] = stage . fold or False
return result , fold
2011-11-12 00:44:05 +00:00
2012-09-13 18:19:52 +00:00
def fields_view_get ( self , cr , user , view_id = None , view_type = ' form ' , context = None , toolbar = False , submenu = False ) :
res = super ( crm_lead , self ) . fields_view_get ( cr , user , view_id , view_type , context , toolbar = toolbar , submenu = submenu )
if view_type == ' form ' :
res [ ' arch ' ] = self . fields_view_get_address ( cr , user , res [ ' arch ' ] , context = context )
return res
2011-11-12 00:44:05 +00:00
2011-11-13 12:07:15 +00:00
_group_by_full = {
' stage_id ' : _read_group_stage_ids
}
2011-11-12 00:44:05 +00:00
2010-11-19 13:48:01 +00:00
def _compute_day ( self , cr , uid , ids , fields , args , context = None ) :
2010-03-22 10:40:26 +00:00
"""
2012-12-05 18:35:45 +00:00
: return dict : difference between current date and log date
2010-04-22 06:40:17 +00:00
"""
cal_obj = self . pool . get ( ' resource.calendar ' )
res_obj = self . pool . get ( ' resource.resource ' )
2010-04-05 06:12:50 +00:00
res = { }
2010-11-19 13:48:01 +00:00
for lead in self . browse ( cr , uid , ids , context = context ) :
2010-04-05 06:12:50 +00:00
for field in fields :
res [ lead . id ] = { }
duration = 0
2010-04-07 11:08:20 +00:00
ans = False
2010-04-05 06:12:50 +00:00
if field == ' day_open ' :
if lead . date_open :
date_create = datetime . strptime ( lead . create_date , " % Y- % m- %d % H: % M: % S " )
date_open = datetime . strptime ( lead . date_open , " % Y- % m- %d % H: % M: % S " )
ans = date_open - date_create
2010-04-07 11:08:20 +00:00
date_until = lead . date_open
2010-04-05 06:12:50 +00:00
elif field == ' day_close ' :
if lead . date_closed :
date_create = datetime . strptime ( lead . create_date , " % Y- % m- %d % H: % M: % S " )
date_close = datetime . strptime ( lead . date_closed , " % Y- % m- %d % H: % M: % S " )
2010-04-07 11:08:20 +00:00
date_until = lead . date_closed
2010-04-05 06:12:50 +00:00
ans = date_close - date_create
2010-04-07 11:08:20 +00:00
if ans :
resource_id = False
if lead . user_id :
resource_ids = res_obj . search ( cr , uid , [ ( ' user_id ' , ' = ' , lead . user_id . id ) ] )
2010-04-09 14:33:13 +00:00
if len ( resource_ids ) :
resource_id = resource_ids [ 0 ]
2010-04-07 11:08:20 +00:00
duration = float ( ans . days )
2010-04-20 05:57:54 +00:00
if lead . section_id and lead . section_id . resource_calendar_id :
2010-05-14 11:33:09 +00:00
duration = float ( ans . days ) * 24
2010-04-07 11:08:20 +00:00
new_dates = cal_obj . interval_get ( cr ,
uid ,
lead . section_id . resource_calendar_id and lead . section_id . resource_calendar_id . id or False ,
2010-09-29 08:05:01 +00:00
datetime . strptime ( lead . create_date , ' % Y- % m- %d % H: % M: % S ' ) ,
2010-04-07 11:08:20 +00:00
duration ,
resource = resource_id
)
no_days = [ ]
2010-09-29 08:05:01 +00:00
date_until = datetime . strptime ( date_until , ' % Y- % m- %d % H: % M: % S ' )
2010-04-07 11:08:20 +00:00
for in_time , out_time in new_dates :
if in_time . date not in no_days :
2010-04-22 06:40:17 +00:00
no_days . append ( in_time . date )
2010-04-07 11:08:20 +00:00
if out_time > date_until :
2010-04-22 06:40:17 +00:00
break
2010-04-07 11:08:20 +00:00
duration = len ( no_days )
2010-04-05 06:12:50 +00:00
res [ lead . id ] [ field ] = abs ( int ( duration ) )
return res
2010-03-10 06:25:59 +00:00
2011-03-24 14:34:28 +00:00
def _history_search ( self , cr , uid , obj , name , args , context = None ) :
res = [ ]
2011-07-22 18:23:37 +00:00
msg_obj = self . pool . get ( ' mail.message ' )
message_ids = msg_obj . search ( cr , uid , [ ( ' email_from ' , ' != ' , False ) , ( ' subject ' , args [ 0 ] [ 1 ] , args [ 0 ] [ 2 ] ) ] , context = context )
2011-03-24 14:34:28 +00:00
lead_ids = self . search ( cr , uid , [ ( ' message_ids ' , ' in ' , message_ids ) ] , context = context )
if lead_ids :
return [ ( ' id ' , ' in ' , lead_ids ) ]
else :
return [ ( ' id ' , ' = ' , ' 0 ' ) ]
2010-01-11 12:57:42 +00:00
_columns = {
2012-12-20 11:47:30 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , ' Partner ' , ondelete = ' set null ' , track_visibility = ' onchange ' ,
2012-10-23 12:02:57 +00:00
select = True , help = " Linked partner (optional). Usually created when converting the lead. " ) ,
2010-10-08 14:40:49 +00:00
2011-10-12 11:11:42 +00:00
' id ' : fields . integer ( ' ID ' , readonly = True ) ,
2012-06-04 08:26:46 +00:00
' name ' : fields . char ( ' Subject ' , size = 64 , required = True , select = 1 ) ,
2010-06-25 17:41:41 +00:00
' active ' : fields . boolean ( ' Active ' , required = False ) ,
2010-05-19 13:42:43 +00:00
' date_action_last ' : fields . datetime ( ' Last Action ' , readonly = 1 ) ,
' date_action_next ' : fields . datetime ( ' Next Action ' , readonly = 1 ) ,
2012-06-25 13:42:53 +00:00
' email_from ' : fields . char ( ' Email ' , size = 128 , help = " Email address of the contact " , select = 1 ) ,
2012-12-18 12:56:34 +00:00
' section_id ' : fields . many2one ( ' crm.case.section ' , ' Sales Team ' ,
2012-12-20 11:47:30 +00:00
select = True , track_visibility = ' onchange ' , help = ' When sending mails, the default email address is taken from the sales team. ' ) ,
2010-05-03 12:30:48 +00:00
' create_date ' : fields . datetime ( ' Creation Date ' , readonly = True ) ,
2010-09-21 16:39:51 +00:00
' 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 " ) ,
2010-05-20 17:34:35 +00:00
' description ' : fields . text ( ' Notes ' ) ,
2010-06-25 17:41:41 +00:00
' write_date ' : fields . datetime ( ' Update Date ' , readonly = True ) ,
2012-07-06 11:06:21 +00:00
' categ_ids ' : fields . many2many ( ' crm.case.categ ' , ' crm_lead_category_rel ' , ' lead_id ' , ' category_id ' , ' Categories ' , \
2010-11-10 07:35:40 +00:00
domain = " [ ' | ' ,( ' section_id ' , ' = ' ,section_id),( ' section_id ' , ' = ' ,False), ( ' object_id.model ' , ' = ' , ' crm.lead ' )] " ) ,
2010-09-21 16:39:51 +00:00
' type_id ' : fields . many2one ( ' crm.case.resource.type ' , ' Campaign ' , \
2011-07-19 09:06:02 +00:00
domain = " [ ' | ' ,( ' section_id ' , ' = ' ,section_id),( ' section_id ' , ' = ' ,False)] " , help = " From which campaign (seminar, marketing campaign, mass mailing, ...) did this contact come from? " ) ,
2011-08-27 23:31:30 +00:00
' channel_id ' : fields . many2one ( ' crm.case.channel ' , ' Channel ' , help = " Communication channel (mail, direct, phone, ...) " ) ,
2011-02-24 15:03:09 +00:00
' contact_name ' : fields . char ( ' Contact Name ' , size = 64 ) ,
2012-03-05 11:03:46 +00:00
' partner_name ' : fields . char ( " Customer Name " , size = 64 , help = ' The name of the future partner company that will be created while converting the lead into opportunity ' , select = 1 ) ,
2013-03-13 11:20:13 +00:00
' opt_out ' : fields . boolean ( ' Opt-Out ' , oldname = ' optout ' ,
help = " If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. "
2013-03-13 12:12:21 +00:00
" Filter ' Available for Mass Mailing ' allows users to filter the leads when performing mass mailing. " ) ,
2011-08-25 04:10:37 +00:00
' type ' : fields . selection ( [ ( ' lead ' , ' Lead ' ) , ( ' opportunity ' , ' Opportunity ' ) , ] , ' Type ' , help = " Type is used to separate Leads and Opportunities " ) ,
2011-12-09 06:03:08 +00:00
' priority ' : fields . selection ( crm . AVAILABLE_PRIORITIES , ' Priority ' , select = True ) ,
2010-04-05 06:12:50 +00:00
' date_closed ' : fields . datetime ( ' Closed ' , readonly = True ) ,
2012-12-20 11:47:30 +00:00
' stage_id ' : fields . many2one ( ' crm.case.stage ' , ' Stage ' , track_visibility = ' onchange ' ,
2012-12-18 13:46:29 +00:00
domain = " [ ' & ' , ' & ' , ( ' fold ' , ' = ' , False), ( ' section_ids ' , ' = ' , section_id), ' | ' , ( ' type ' , ' = ' , type), ( ' type ' , ' = ' , ' both ' )] " ) ,
2012-12-20 11:47:30 +00:00
' user_id ' : fields . many2one ( ' res.users ' , ' Salesperson ' , select = True , track_visibility = ' onchange ' ) ,
2010-04-18 15:32:44 +00:00
' referred ' : fields . char ( ' Referred By ' , size = 64 ) ,
2010-04-05 08:40:54 +00:00
' date_open ' : fields . datetime ( ' Opened ' , readonly = True ) ,
2010-04-05 06:12:50 +00:00
' day_open ' : fields . function ( _compute_day , string = ' Days to Open ' , \
2011-07-05 12:28:57 +00:00
multi = ' day_open ' , type = " float " , store = True ) ,
2010-04-05 06:12:50 +00:00
' day_close ' : fields . function ( _compute_day , string = ' Days to Close ' , \
2011-07-05 12:28:57 +00:00
multi = ' day_close ' , type = " float " , store = True ) ,
2012-05-21 12:40:46 +00:00
' state ' : fields . related ( ' stage_id ' , ' state ' , type = " selection " , store = True ,
2012-10-12 11:42:58 +00:00
selection = crm . AVAILABLE_STATES , string = " Status " , readonly = True ,
2012-10-29 17:37:14 +00:00
help = ' The Status is set to \' Draft \' , when a case is created. If the case is in progress the Status is set to \' Open \' . When the case is over, the Status is set to \' Done \' . If the case needs to be reviewed then the Status is set to \' Pending \' . ' ) ,
2010-02-28 10:57:24 +00:00
2011-08-25 04:10:37 +00:00
# Only used for type opportunity
2012-05-04 10:45:06 +00:00
' probability ' : fields . float ( ' Success Rate ( % ) ' , group_operator = " avg " ) ,
2012-12-20 11:47:30 +00:00
' planned_revenue ' : fields . float ( ' Expected Revenue ' , track_visibility = ' always ' ) ,
2011-08-25 04:10:37 +00:00
' ref ' : fields . reference ( ' Reference ' , selection = crm . _links_get , size = 128 ) ,
' ref2 ' : fields . reference ( ' Reference 2 ' , selection = crm . _links_get , size = 128 ) ,
' phone ' : fields . char ( " Phone " , size = 64 ) ,
2012-11-02 16:26:11 +00:00
' date_deadline ' : fields . date ( ' Expected Closing ' , help = " Estimate of the date on which the opportunity will be won. " ) ,
2011-12-09 06:03:08 +00:00
' date_action ' : fields . date ( ' Next Action Date ' , select = True ) ,
2011-08-25 04:10:37 +00:00
' title_action ' : fields . char ( ' Next Action ' , size = 64 ) ,
2011-09-13 16:29:07 +00:00
' color ' : fields . integer ( ' Color Index ' ) ,
2012-02-29 12:41:42 +00:00
' partner_address_name ' : fields . related ( ' partner_id ' , ' name ' , type = ' char ' , string = ' Partner Contact Name ' , readonly = True ) ,
' partner_address_email ' : fields . related ( ' partner_id ' , ' email ' , type = ' char ' , string = ' Partner Contact Email ' , readonly = True ) ,
2012-11-24 17:53:19 +00:00
' company_currency ' : fields . related ( ' company_id ' , ' currency_id ' , type = ' many2one ' , string = ' Currency ' , readonly = True , relation = " res.currency " ) ,
2012-08-10 14:43:39 +00:00
' user_email ' : fields . related ( ' user_id ' , ' email ' , type = ' char ' , string = ' User Email ' , readonly = True ) ,
2011-09-13 16:29:07 +00:00
' user_login ' : fields . related ( ' user_id ' , ' login ' , type = ' char ' , string = ' User Login ' , readonly = True ) ,
2012-06-28 14:05:16 +00:00
# Fields for address, due to separation from crm and res.partner
' street ' : fields . char ( ' Street ' , size = 128 ) ,
' street2 ' : fields . char ( ' Street2 ' , size = 128 ) ,
' zip ' : fields . char ( ' Zip ' , change_default = True , size = 24 ) ,
' city ' : fields . char ( ' City ' , size = 128 ) ,
2012-10-30 09:08:06 +00:00
' state_id ' : fields . many2one ( " res.country.state " , ' State ' ) ,
2012-06-28 14:05:16 +00:00
' country_id ' : fields . many2one ( ' res.country ' , ' Country ' ) ,
' phone ' : fields . char ( ' Phone ' , size = 64 ) ,
' fax ' : fields . char ( ' Fax ' , size = 64 ) ,
' mobile ' : fields . char ( ' Mobile ' , size = 64 ) ,
' function ' : fields . char ( ' Function ' , size = 128 ) ,
2012-06-28 15:12:32 +00:00
' title ' : fields . many2one ( ' res.partner.title ' , ' Title ' ) ,
2012-06-28 14:05:16 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , select = 1 ) ,
2012-07-31 10:40:43 +00:00
' payment_mode ' : fields . many2one ( ' crm.payment.mode ' , ' Payment Mode ' , \
2012-07-31 09:49:32 +00:00
domain = " [( ' section_id ' , ' = ' ,section_id)] " ) ,
' planned_cost ' : fields . float ( ' Planned Costs ' ) ,
2011-08-25 04:10:37 +00:00
}
2010-02-28 13:21:56 +00:00
_defaults = {
2012-04-19 13:33:09 +00:00
' active ' : 1 ,
2012-05-25 15:20:46 +00:00
' type ' : ' lead ' ,
2012-05-24 14:26:16 +00:00
' user_id ' : lambda s , cr , uid , c : s . _get_default_user ( cr , uid , c ) ,
' email_from ' : lambda s , cr , uid , c : s . _get_default_email ( cr , uid , c ) ,
' stage_id ' : lambda s , cr , uid , c : s . _get_default_stage_id ( cr , uid , c ) ,
' section_id ' : lambda s , cr , uid , c : s . _get_default_section_id ( cr , uid , c ) ,
2010-04-05 06:12:50 +00:00
' company_id ' : lambda s , cr , uid , c : s . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' crm.lead ' , context = c ) ,
' priority ' : lambda * a : crm . AVAILABLE_PRIORITIES [ 2 ] [ 0 ] ,
2011-09-13 16:29:07 +00:00
' color ' : 0 ,
2010-02-28 13:21:56 +00:00
}
2011-02-24 15:03:09 +00:00
2012-11-19 10:08:43 +00:00
_sql_constraints = [
2012-11-30 10:05:16 +00:00
( ' check_probability ' , ' check(probability >= 0 and probability <= 100) ' , ' The probability of closing the deal should be between 0 % a nd 100 % ! ' )
2012-11-19 10:08:43 +00:00
]
2012-03-05 18:40:03 +00:00
def onchange_stage_id ( self , cr , uid , ids , stage_id , context = None ) :
2011-08-25 04:10:37 +00:00
if not stage_id :
return { ' value ' : { } }
stage = self . pool . get ( ' crm.case.stage ' ) . browse ( cr , uid , stage_id , context )
if not stage . on_change :
return { ' value ' : { } }
return { ' value ' : { ' probability ' : stage . probability } }
2012-08-03 07:40:51 +00:00
def on_change_partner ( self , cr , uid , ids , partner_id , context = None ) :
result = { }
2012-08-23 14:35:21 +00:00
values = { }
2012-08-03 07:40:51 +00:00
if partner_id :
partner = self . pool . get ( ' res.partner ' ) . browse ( cr , uid , partner_id , context = context )
2012-08-08 09:08:46 +00:00
values = {
2012-09-26 12:44:43 +00:00
' partner_name ' : partner . name ,
2012-08-08 09:08:46 +00:00
' street ' : partner . street ,
' street2 ' : partner . street2 ,
' city ' : partner . city ,
' state_id ' : partner . state_id and partner . state_id . id or False ,
' country_id ' : partner . country_id and partner . country_id . id or False ,
2012-09-18 08:35:35 +00:00
' email_from ' : partner . email ,
' phone ' : partner . phone ,
' mobile ' : partner . mobile ,
' fax ' : partner . fax ,
2012-08-08 09:08:46 +00:00
}
return { ' value ' : values }
2012-08-03 07:40:51 +00:00
2012-05-29 14:03:14 +00:00
def _check ( self , cr , uid , ids = False , context = None ) :
""" Override of the base.stage method.
Function called by the scheduler to process cases for date actions
Only works on not done and cancelled cases
2010-05-05 11:55:13 +00:00
"""
2012-05-29 14:03:14 +00:00
cr . execute ( ' select * from crm_case \
where ( date_action_last < % s or date_action_last is null ) \
and ( date_action_next < = % s or date_action_next is null ) \
and state not in ( \' cancel \' , \' done \' ) ' ,
( time . strftime ( " % Y- % m- %d % H: % M: % S " ) ,
time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ) )
ids2 = map ( lambda x : x [ 0 ] , cr . fetchall ( ) or [ ] )
cases = self . browse ( cr , uid , ids2 , context = context )
return self . _action ( cr , uid , cases , False , context = context )
2012-10-29 18:11:56 +00:00
def stage_find ( self , cr , uid , cases , section_id , domain = None , order = ' sequence ' , context = None ) :
2012-05-24 14:26:16 +00:00
""" Override of the base.stage method
Parameter of the stage search taken from the lead :
- type : stage type must be the same or ' both '
- section_id : if set , stages must belong to this section or
2012-05-25 15:20:46 +00:00
be a default stage ; if not set , stages must be default
stages
2010-05-05 11:55:13 +00:00
"""
2012-05-24 14:26:16 +00:00
if isinstance ( cases , ( int , long ) ) :
cases = self . browse ( cr , uid , cases , context = context )
2012-05-31 14:19:30 +00:00
# collect all section_ids
section_ids = [ ]
types = [ ' both ' ]
2012-10-31 12:17:20 +00:00
if not cases :
2012-11-07 12:56:15 +00:00
type = context . get ( ' default_type ' )
types + = [ type ]
2012-05-24 14:26:16 +00:00
if section_id :
2012-05-31 14:19:30 +00:00
section_ids . append ( section_id )
2012-05-24 14:26:16 +00:00
for lead in cases :
2012-05-31 14:19:30 +00:00
if lead . section_id :
section_ids . append ( lead . section_id . id )
if lead . type not in types :
types . append ( lead . type )
# OR all section_ids and OR with case_default
search_domain = [ ]
if section_ids :
search_domain + = [ ( ' | ' ) ] * len ( section_ids )
for section_id in section_ids :
search_domain . append ( ( ' section_ids ' , ' = ' , section_id ) )
2013-01-07 17:30:37 +00:00
else :
search_domain . append ( ( ' case_default ' , ' = ' , True ) )
2012-05-31 14:19:30 +00:00
# AND with cases types
search_domain . append ( ( ' type ' , ' in ' , types ) )
# AND with the domain in parameter
search_domain + = list ( domain )
# perform search, return the first found
stage_ids = self . pool . get ( ' crm.case.stage ' ) . search ( cr , uid , search_domain , order = order , context = context )
2012-05-24 14:26:16 +00:00
if stage_ids :
return stage_ids [ 0 ]
2011-08-25 04:10:37 +00:00
return False
2011-02-24 15:03:09 +00:00
2012-02-23 10:46:41 +00:00
def case_cancel ( self , cr , uid , ids , context = None ) :
2012-05-29 14:56:01 +00:00
""" Overrides case_cancel from base_stage to set probability """
2012-05-21 14:02:16 +00:00
res = super ( crm_lead , self ) . case_cancel ( cr , uid , ids , context = context )
self . write ( cr , uid , ids , { ' probability ' : 0.0 } , context = context )
2010-05-05 11:55:13 +00:00
return res
2010-05-03 12:30:48 +00:00
2012-02-23 10:46:41 +00:00
def case_reset ( self , cr , uid , ids , context = None ) :
2012-05-29 14:56:01 +00:00
""" Overrides case_reset from base_stage to set probability """
2012-05-21 14:02:16 +00:00
res = super ( crm_lead , self ) . case_reset ( cr , uid , ids , context = context )
self . write ( cr , uid , ids , { ' probability ' : 0.0 } , context = context )
2011-08-25 04:10:37 +00:00
return res
2012-02-23 10:46:41 +00:00
def case_mark_lost ( self , cr , uid , ids , context = None ) :
2012-05-29 14:56:01 +00:00
""" Mark the case as lost: state=cancel and probability=0 """
2012-02-23 10:46:41 +00:00
for lead in self . browse ( cr , uid , ids ) :
2013-01-07 17:30:37 +00:00
stage_id = self . stage_find ( cr , uid , [ lead ] , lead . section_id . id or False , [ ( ' probability ' , ' = ' , 0.0 ) , ( ' on_change ' , ' = ' , True ) ] , context = context )
2011-08-25 04:10:37 +00:00
if stage_id :
2012-05-21 14:02:16 +00:00
self . case_set ( cr , uid , [ lead . id ] , values_to_update = { ' probability ' : 0.0 } , new_stage_id = stage_id , context = context )
return True
2011-08-25 04:10:37 +00:00
2012-02-23 10:46:41 +00:00
def case_mark_won ( self , cr , uid , ids , context = None ) :
2013-01-07 17:30:37 +00:00
""" Mark the case as won: state=done and probability=100 """
2012-02-23 10:46:41 +00:00
for lead in self . browse ( cr , uid , ids ) :
2013-01-07 17:30:37 +00:00
stage_id = self . stage_find ( cr , uid , [ lead ] , lead . section_id . id or False , [ ( ' probability ' , ' = ' , 100.0 ) , ( ' on_change ' , ' = ' , True ) ] , context = context )
2011-08-25 04:10:37 +00:00
if stage_id :
2012-05-21 14:23:17 +00:00
self . case_set ( cr , uid , [ lead . id ] , values_to_update = { ' probability ' : 100.0 } , new_stage_id = stage_id , context = context )
2012-05-21 14:02:16 +00:00
return True
2011-08-25 04:10:37 +00:00
2011-09-13 16:29:07 +00:00
def set_priority ( self , cr , uid , ids , priority ) :
2012-05-30 09:03:40 +00:00
""" Set lead priority
2011-09-13 16:29:07 +00:00
"""
return self . write ( cr , uid , ids , { ' priority ' : priority } )
2012-02-23 10:46:41 +00:00
def set_high_priority ( self , cr , uid , ids , context = None ) :
2012-05-30 09:03:40 +00:00
""" Set lead priority to high
2011-09-13 16:29:07 +00:00
"""
return self . set_priority ( cr , uid , ids , ' 1 ' )
2012-02-23 10:46:41 +00:00
def set_normal_priority ( self , cr , uid , ids , context = None ) :
2012-05-30 09:03:40 +00:00
""" Set lead priority to normal
2011-09-13 16:29:07 +00:00
"""
return self . set_priority ( cr , uid , ids , ' 3 ' )
2012-12-04 15:32:22 +00:00
def _merge_get_result_type ( self , cr , uid , opps , context = None ) :
"""
Define the type of the result of the merge . If at least one of the
element to merge is an opp , the resulting new element will be an opp .
Otherwise it will be a lead .
We ' ll directly use a list of browse records instead of a list of ids
for performances ' sake: it will spare a second browse of the
leads / opps .
: param list opps : list of browse records containing the leads / opps to process
: return string type : the type of the final element
"""
2012-12-05 16:19:35 +00:00
for opp in opps :
2012-12-04 15:32:22 +00:00
if ( opp . type == ' opportunity ' ) :
return ' opportunity '
return ' lead '
2011-10-26 09:21:33 +00:00
def _merge_data ( self , cr , uid , ids , oldest , fields , context = None ) :
2012-11-26 17:49:18 +00:00
"""
Prepare lead / opp data into a dictionary for merging . Different types
of fields are processed in different ways :
- text : all the values are concatenated
- m2m and o2m : those fields aren ' t processed
- m2o : the first not null value prevails ( the other are dropped )
- any other type of field : same as m2o
: param list ids : list of ids of the leads to process
: param list fields : list of leads ' fields to process
: return dict data : contains the merged values
"""
2011-10-21 07:01:11 +00:00
opportunities = self . browse ( cr , uid , ids , context = context )
2012-11-26 17:49:18 +00:00
2011-10-21 07:01:11 +00:00
def _get_first_not_null ( attr ) :
2012-11-26 17:49:18 +00:00
for opp in opportunities :
2013-02-05 11:30:24 +00:00
if hasattr ( opp , attr ) and bool ( getattr ( opp , attr ) ) :
2012-11-26 17:49:18 +00:00
return getattr ( opp , attr )
2011-10-21 07:01:11 +00:00
return False
def _get_first_not_null_id ( attr ) :
res = _get_first_not_null ( attr )
return res and res . id or False
2011-12-31 07:57:20 +00:00
2011-10-21 07:01:11 +00:00
def _concat_all ( attr ) :
2013-02-06 13:22:19 +00:00
return ' \n \n ' . join ( filter ( lambda x : x , [ getattr ( opp , attr ) or ' ' for opp in opportunities if hasattr ( opp , attr ) ] ) )
2012-11-26 17:49:18 +00:00
# Process the fields' values
2011-10-26 09:21:33 +00:00
data = { }
for field_name in fields :
field_info = self . _all_columns . get ( field_name )
if field_info is None :
continue
field = field_info . column
if field . _type in ( ' many2many ' , ' one2many ' ) :
2011-12-31 07:57:20 +00:00
continue
2011-10-26 09:21:33 +00:00
elif field . _type == ' many2one ' :
data [ field_name ] = _get_first_not_null_id ( field_name ) # !!
elif field . _type == ' text ' :
data [ field_name ] = _concat_all ( field_name ) #not lost
else :
data [ field_name ] = _get_first_not_null ( field_name ) #not lost
2012-11-26 17:49:18 +00:00
# Define the resulting type ('lead' or 'opportunity')
2012-12-04 15:32:22 +00:00
data [ ' type ' ] = self . _merge_get_result_type ( cr , uid , opportunities , context )
2011-10-21 07:01:11 +00:00
return data
2012-08-17 12:06:06 +00:00
def _mail_body ( self , cr , uid , lead , fields , title = False , context = None ) :
2011-10-26 09:21:33 +00:00
body = [ ]
if title :
body . append ( " %s \n " % ( title ) )
2012-12-04 17:42:31 +00:00
2011-10-26 09:21:33 +00:00
for field_name in fields :
field_info = self . _all_columns . get ( field_name )
if field_info is None :
continue
field = field_info . column
2012-11-28 05:53:37 +00:00
value = ' '
2011-10-26 09:21:33 +00:00
if field . _type == ' selection ' :
if hasattr ( field . selection , ' __call__ ' ) :
key = field . selection ( self , cr , uid , context = context )
else :
key = field . selection
value = dict ( key ) . get ( lead [ field_name ] , lead [ field_name ] )
elif field . _type == ' many2one ' :
if lead [ field_name ] :
value = lead [ field_name ] . name_get ( ) [ 0 ] [ 1 ]
2012-11-26 09:07:33 +00:00
elif field . _type == ' many2many ' :
if lead [ field_name ] :
2012-11-27 08:54:21 +00:00
for val in lead [ field_name ] :
field_value = val . name_get ( ) [ 0 ] [ 1 ]
2012-11-28 05:36:58 +00:00
value + = field_value + " , "
2011-10-26 09:21:33 +00:00
else :
value = lead [ field_name ]
2011-11-30 12:06:40 +00:00
body . append ( " %s : %s " % ( field . string , value or ' ' ) )
2012-11-29 13:17:20 +00:00
return " <br/> " . join ( body + [ ' <br/> ' ] )
2011-10-26 09:21:33 +00:00
2012-12-04 17:42:31 +00:00
def _merge_notify ( self , cr , uid , opportunity_id , opportunities , context = None ) :
"""
Create a message gathering merged leads / opps information .
"""
2011-10-21 07:01:11 +00:00
#TOFIX: mail template should be used instead of fix body, subject text
details = [ ]
2012-12-04 15:32:22 +00:00
result_type = self . _merge_get_result_type ( cr , uid , opportunities , context )
2012-12-08 08:30:38 +00:00
if result_type == ' lead ' :
merge_message = _ ( ' Merged leads ' )
else :
merge_message = _ ( ' Merged opportunities ' )
2011-10-21 07:01:11 +00:00
subject = [ merge_message ]
for opportunity in opportunities :
subject . append ( opportunity . name )
2013-02-06 11:15:36 +00:00
title = " %s : %s " % ( opportunity . type == ' opportunity ' and _ ( ' Merged opportunity ' ) or _ ( ' Merged lead ' ) , opportunity . name )
2013-02-08 17:20:25 +00:00
fields = list ( CRM_LEAD_FIELDS_TO_MERGE )
details . append ( self . _mail_body ( cr , uid , opportunity , fields , title = title , context = context ) )
2011-12-31 07:57:20 +00:00
2012-09-26 13:54:05 +00:00
# Chatter message's subject
subject = subject [ 0 ] + " : " + " , " . join ( subject [ 1 : ] )
2011-10-21 07:01:11 +00:00
details = " \n \n " . join ( details )
2012-08-22 11:34:39 +00:00
return self . message_post ( cr , uid , [ opportunity_id ] , body = details , subject = subject , context = context )
2011-12-31 07:57:20 +00:00
2011-10-21 07:01:11 +00:00
def _merge_opportunity_history ( self , cr , uid , opportunity_id , opportunities , context = None ) :
message = self . pool . get ( ' mail.message ' )
for opportunity in opportunities :
for history in opportunity . message_ids :
message . write ( cr , uid , history . id , {
2011-12-31 07:57:20 +00:00
' res_id ' : opportunity_id ,
' subject ' : _ ( " From %s : %s " ) % ( opportunity . name , history . subject )
2011-10-21 07:01:11 +00:00
} , context = context )
return True
def _merge_opportunity_attachments ( self , cr , uid , opportunity_id , opportunities , context = None ) :
2013-02-20 12:40:52 +00:00
attach_obj = self . pool . get ( ' ir.attachment ' )
2011-10-21 07:01:11 +00:00
# return attachments of opportunity
def _get_attachments ( opportunity_id ) :
2013-02-20 12:40:52 +00:00
attachment_ids = attach_obj . search ( cr , uid , [ ( ' res_model ' , ' = ' , self . _name ) , ( ' res_id ' , ' = ' , opportunity_id ) ] , context = context )
return attach_obj . browse ( cr , uid , attachment_ids , context = context )
2011-10-21 07:01:11 +00:00
first_attachments = _get_attachments ( opportunity_id )
2013-02-20 14:09:36 +00:00
#counter of all attachments to move. Used to make sure the name is different for all attachments
count = 1
2011-10-21 07:01:11 +00:00
for opportunity in opportunities :
attachments = _get_attachments ( opportunity . id )
2013-02-20 12:40:52 +00:00
for attachment in attachments :
2013-02-20 14:09:36 +00:00
values = { ' res_id ' : opportunity_id , }
for attachment_in_first in first_attachments :
if attachment . name == attachment_in_first . name :
name = " %s ( %s ) " % ( attachment . name , count , ) ,
count + = 1
attachment . write ( values )
2011-12-31 07:57:20 +00:00
return True
2011-10-21 07:01:11 +00:00
def merge_opportunity ( self , cr , uid , ids , context = None ) :
"""
2012-11-20 18:13:33 +00:00
Different cases of merge :
- merge leads together = 1 new lead
- merge at least 1 opp with anything else ( lead or opp ) = 1 new opp
2012-11-26 17:49:18 +00:00
: param list ids : leads / opportunities ids to merge
2012-11-20 18:13:33 +00:00
: return int id : id of the resulting lead / opp
2011-10-21 07:01:11 +00:00
"""
2013-02-05 16:35:02 +00:00
if context is None :
context = { }
2011-12-31 07:57:20 +00:00
2011-10-21 07:01:11 +00:00
if len ( ids ) < = 1 :
2013-02-05 16:35:02 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' Please select more than one element (lead or opportunity) from the list view. ' ) )
opportunities = self . browse ( cr , uid , ids , context = context )
sequenced_opps = [ ]
for opportunity in opportunities :
2013-02-12 13:36:32 +00:00
if opportunity . stage_id and opportunity . stage_id . state != ' cancel ' :
sequenced_opps . append ( ( opportunity . stage_id . sequence , opportunity ) )
else :
sequenced_opps . append ( ( - 1 , opportunity ) )
2013-02-05 16:35:02 +00:00
sequenced_opps . sort ( key = lambda tup : tup [ 0 ] , reverse = True )
opportunities = [ opportunity for sequence , opportunity in sequenced_opps ]
ids = [ opportunity . id for opportunity in opportunities ]
highest = opportunities [ 0 ]
opportunities_rest = opportunities [ 1 : ]
2013-02-04 17:51:06 +00:00
tail_opportunities = opportunities_rest
2011-10-21 07:01:11 +00:00
2013-02-08 17:20:25 +00:00
fields = list ( CRM_LEAD_FIELDS_TO_MERGE )
merged_data = self . _merge_data ( cr , uid , ids , highest , fields , context = context )
2011-10-21 07:01:11 +00:00
2012-09-26 12:44:43 +00:00
# Merge messages and attachements into the first opportunity
2013-02-05 16:35:02 +00:00
self . _merge_opportunity_history ( cr , uid , highest . id , tail_opportunities , context = context )
self . _merge_opportunity_attachments ( cr , uid , highest . id , tail_opportunities , context = context )
2011-12-31 07:57:20 +00:00
2012-09-26 12:44:43 +00:00
# Merge notifications about loss of information
2013-02-05 16:35:02 +00:00
opportunities = [ highest ]
2013-02-05 11:30:24 +00:00
opportunities . extend ( opportunities_rest )
2013-02-05 16:35:02 +00:00
self . _merge_notify ( cr , uid , highest , opportunities , context = context )
2013-02-06 11:06:56 +00:00
# Check if the stage is in the stages of the sales team. If not, assign the stage with the lowest sequence
if merged_data . get ( ' type ' ) == ' opportunity ' and merged_data . get ( ' section_id ' ) :
section_stages = self . pool . get ( ' crm.case.section ' ) . read ( cr , uid , merged_data [ ' section_id ' ] , [ ' stage_ids ' ] , context = context )
if merged_data . get ( ' stage_id ' ) not in section_stages [ ' stage_ids ' ] :
stages_sequences = self . pool . get ( ' crm.case.stage ' ) . search ( cr , uid , [ ( ' id ' , ' in ' , section_stages [ ' stage_ids ' ] ) ] , order = ' sequence ' , limit = 1 , context = context )
2013-02-12 13:36:32 +00:00
merged_data [ ' stage_id ' ] = stages_sequences and stages_sequences [ 0 ] or False
2012-09-26 12:44:43 +00:00
# Write merged data into first opportunity
2013-02-05 16:35:02 +00:00
self . write ( cr , uid , [ highest . id ] , merged_data , context = context )
2012-09-26 12:44:43 +00:00
# Delete tail opportunities
2011-10-21 07:01:11 +00:00
self . unlink ( cr , uid , [ x . id for x in tail_opportunities ] , context = context )
2013-02-05 16:35:02 +00:00
return highest . id
2011-10-21 07:01:11 +00:00
2011-10-24 13:30:10 +00:00
def _convert_opportunity_data ( self , cr , uid , lead , customer , section_id = False , context = None ) :
2011-10-21 07:01:11 +00:00
crm_stage = self . pool . get ( ' crm.case.stage ' )
2011-11-23 07:20:20 +00:00
contact_id = False
if customer :
contact_id = self . pool . get ( ' res.partner ' ) . address_get ( cr , uid , [ customer . id ] ) [ ' default ' ]
2011-10-24 13:30:10 +00:00
if not section_id :
section_id = lead . section_id and lead . section_id . id or False
2013-01-07 17:30:37 +00:00
val = {
2012-11-27 16:49:40 +00:00
' planned_revenue ' : lead . planned_revenue ,
' probability ' : lead . probability ,
' name ' : lead . name ,
' partner_id ' : customer and customer . id or False ,
' user_id ' : ( lead . user_id and lead . user_id . id ) ,
' type ' : ' opportunity ' ,
2012-12-04 15:33:51 +00:00
' date_action ' : fields . datetime . now ( ) ,
' date_open ' : fields . datetime . now ( ) ,
2012-11-27 16:49:40 +00:00
' email_from ' : customer and customer . email or lead . email_from ,
' phone ' : customer and customer . phone or lead . phone ,
2011-10-21 07:01:11 +00:00
}
2013-01-07 17:30:37 +00:00
if not lead . stage_id or lead . stage_id . type == ' lead ' :
val [ ' stage_id ' ] = self . stage_find ( cr , uid , [ lead ] , section_id , [ ( ' state ' , ' = ' , ' draft ' ) , ( ' type ' , ' in ' , ( ' opportunity ' , ' both ' ) ) ] , context = context )
return val
2012-01-25 16:04:17 +00:00
2011-10-24 13:30:10 +00:00
def convert_opportunity ( self , cr , uid , ids , partner_id , user_ids = False , section_id = False , context = None ) :
2011-11-23 07:20:20 +00:00
customer = False
if partner_id :
2012-11-06 17:51:42 +00:00
partner = self . pool . get ( ' res.partner ' )
2011-11-23 07:20:20 +00:00
customer = partner . browse ( cr , uid , partner_id , context = context )
2011-10-21 07:01:11 +00:00
for lead in self . browse ( cr , uid , ids , context = context ) :
2011-10-24 13:30:10 +00:00
if lead . state in ( ' done ' , ' cancel ' ) :
continue
vals = self . _convert_opportunity_data ( cr , uid , lead , customer , section_id , context = context )
2011-10-21 07:01:11 +00:00
self . write ( cr , uid , [ lead . id ] , vals , context = context )
2012-12-19 16:40:48 +00:00
self . message_post ( cr , uid , ids , body = _ ( " Lead <b>converted into an Opportunity</b> " ) , subtype = " crm.mt_lead_convert_to_opportunity " , context = context )
2012-08-23 20:06:29 +00:00
if user_ids or section_id :
self . allocate_salesman ( cr , uid , ids , user_ids , section_id , context = context )
2011-12-31 07:57:20 +00:00
2011-10-24 13:30:10 +00:00
return True
2011-10-21 07:01:11 +00:00
2012-03-05 11:03:46 +00:00
def _lead_create_contact ( self , cr , uid , lead , name , is_company , parent_id = False , context = None ) :
2011-10-21 07:01:11 +00:00
partner = self . pool . get ( ' res.partner ' )
2013-01-31 15:28:20 +00:00
vals = { ' name ' : name ,
2012-03-05 11:03:46 +00:00
' user_id ' : lead . user_id . id ,
' comment ' : lead . description ,
' section_id ' : lead . section_id . id or False ,
' parent_id ' : parent_id ,
' phone ' : lead . phone ,
' mobile ' : lead . mobile ,
2013-02-27 12:15:06 +00:00
' email ' : tools . email_split ( lead . email_from ) and tools . email_split ( lead . email_from ) [ 0 ] or False ,
2012-03-05 11:03:46 +00:00
' fax ' : lead . fax ,
' title ' : lead . title and lead . title . id or False ,
' function ' : lead . function ,
' street ' : lead . street ,
' street2 ' : lead . street2 ,
' zip ' : lead . zip ,
' city ' : lead . city ,
' country_id ' : lead . country_id and lead . country_id . id or False ,
' state_id ' : lead . state_id and lead . state_id . id or False ,
' is_company ' : is_company ,
2012-03-06 11:12:07 +00:00
' type ' : ' contact '
2012-03-05 11:03:46 +00:00
}
2013-01-31 15:28:20 +00:00
partner = partner . create ( cr , uid , vals , context = context )
2012-03-05 11:03:46 +00:00
return partner
def _create_lead_partner ( self , cr , uid , lead , context = None ) :
2013-01-31 15:28:20 +00:00
partner_id = False
2012-03-05 11:03:46 +00:00
if lead . partner_name and lead . contact_name :
2012-03-09 04:48:43 +00:00
partner_id = self . _lead_create_contact ( cr , uid , lead , lead . partner_name , True , context = context )
2012-08-23 19:31:30 +00:00
partner_id = self . _lead_create_contact ( cr , uid , lead , lead . contact_name , False , partner_id , context = context )
2012-03-05 11:03:46 +00:00
elif lead . partner_name and not lead . contact_name :
2012-03-09 04:48:43 +00:00
partner_id = self . _lead_create_contact ( cr , uid , lead , lead . partner_name , True , context = context )
2012-03-05 11:03:46 +00:00
elif not lead . partner_name and lead . contact_name :
2012-03-09 04:48:43 +00:00
partner_id = self . _lead_create_contact ( cr , uid , lead , lead . contact_name , False , context = context )
2013-02-01 12:04:24 +00:00
elif lead . email_from and self . pool . get ( ' res.partner ' ) . _parse_partner_name ( lead . email_from , context = context ) [ 0 ] :
contact_name = self . pool . get ( ' res.partner ' ) . _parse_partner_name ( lead . email_from , context = context ) [ 0 ]
2013-01-31 15:28:20 +00:00
partner_id = self . _lead_create_contact ( cr , uid , lead , contact_name , False , context = context )
2012-03-05 11:03:46 +00:00
else :
2013-02-01 12:04:24 +00:00
raise osv . except_osv (
_ ( ' Warning! ' ) ,
_ ( ' No customer name defined. Please fill one of the following fields: Company Name, Contact Name or Email ( " Name <email@address> " ) ' )
)
2011-10-21 07:01:11 +00:00
return partner_id
2011-10-26 16:27:27 +00:00
def _lead_set_partner ( self , cr , uid , lead , partner_id , context = None ) :
2012-12-05 17:18:15 +00:00
"""
Assign a partner to a lead .
: param object lead : browse record of the lead to process
: param int partner_id : identifier of the partner to assign
: return bool : True if the partner has properly been assigned
"""
2011-10-26 09:21:33 +00:00
res = False
res_partner = self . pool . get ( ' res.partner ' )
if partner_id :
2013-02-04 17:51:06 +00:00
res_partner . write ( cr , uid , partner_id , { ' section_id ' : lead . section_id and lead . section_id . id or False } )
2011-10-26 09:21:33 +00:00
contact_id = res_partner . address_get ( cr , uid , [ partner_id ] ) [ ' default ' ]
2012-12-05 17:18:15 +00:00
res = lead . write ( { ' partner_id ' : partner_id } , context = context )
2012-12-19 16:40:48 +00:00
message = _ ( " <b>Partner</b> set to <em> %s </em>. " % ( lead . partner_id . name ) )
2012-12-18 13:21:13 +00:00
self . message_post ( cr , uid , [ lead . id ] , body = message , context = context )
2011-10-26 09:21:33 +00:00
return res
2011-10-21 07:01:11 +00:00
2012-12-05 19:21:46 +00:00
def handle_partner_assignation ( self , cr , uid , ids , action = ' create ' , partner_id = False , context = None ) :
2011-10-21 07:01:11 +00:00
"""
2012-12-05 17:18:15 +00:00
Handle partner assignation during a lead conversion .
2011-10-26 16:27:27 +00:00
if action is ' create ' , create new partner with contact and assign lead to new partner_id .
2012-12-05 19:21:46 +00:00
otherwise assign lead to the specified partner_id
2012-12-05 17:18:15 +00:00
: param list ids : leads / opportunities ids to process
: param string action : what has to be done regarding partners ( create it , assign an existing one , or nothing )
: param int partner_id : partner to assign if any
: return dict : dictionary organized as followed : { lead_id : partner_assigned_id }
2011-10-21 07:01:11 +00:00
"""
2012-12-05 19:24:54 +00:00
#TODO this is a duplication of the handle_partner_assignation method of crm_phonecall
2011-10-24 13:30:10 +00:00
partner_ids = { }
2012-12-06 14:04:28 +00:00
# If a partner_id is given, force this partner for all elements
force_partner_id = partner_id
2011-10-21 07:01:11 +00:00
for lead in self . browse ( cr , uid , ids , context = context ) :
2012-12-05 17:18:15 +00:00
# If the action is set to 'create' and no partner_id is set, create a new one
2012-12-06 14:04:28 +00:00
if action == ' create ' :
partner_id = force_partner_id or self . _create_lead_partner ( cr , uid , lead , context )
2011-10-26 16:27:27 +00:00
self . _lead_set_partner ( cr , uid , lead , partner_id , context = context )
2011-10-24 13:30:10 +00:00
partner_ids [ lead . id ] = partner_id
2011-10-21 07:01:11 +00:00
return partner_ids
2012-11-27 16:54:01 +00:00
def allocate_salesman ( self , cr , uid , ids , user_ids = None , team_id = False , context = None ) :
2012-11-27 14:26:28 +00:00
"""
Assign salesmen and salesteam to a batch of leads . If there are more
leads than salesmen , these salesmen will be assigned in round - robin .
E . g . : 4 salesmen ( S1 , S2 , S3 , S4 ) for 6 leads ( L1 , L2 , . . . L6 ) . They
will be assigned as followed : L1 - S1 , L2 - S2 , L3 - S3 , L4 - S4 ,
L5 - S1 , L6 - S2 .
: param list ids : leads / opportunities ids to process
: param list user_ids : salesmen to assign
: param int team_id : salesteam to assign
: return bool
"""
2011-10-21 07:01:11 +00:00
index = 0
2012-11-27 16:54:01 +00:00
2011-10-21 07:01:11 +00:00
for lead_id in ids :
value = { }
if team_id :
value [ ' section_id ' ] = team_id
2012-11-27 16:54:01 +00:00
if user_ids :
2011-10-21 07:01:11 +00:00
value [ ' user_id ' ] = user_ids [ index ]
2012-11-27 16:54:01 +00:00
# Cycle through user_ids
2012-04-25 17:48:05 +00:00
index = ( index + 1 ) % len ( user_ids )
2011-10-21 07:01:11 +00:00
if value :
self . write ( cr , uid , [ lead_id ] , value , context = context )
return True
2011-11-25 06:27:03 +00:00
def schedule_phonecall ( self , cr , uid , ids , schedule_time , call_summary , desc , phone , contact_name , user_id = False , section_id = False , categ_id = False , action = ' schedule ' , context = None ) :
2011-10-24 13:30:10 +00:00
"""
2012-11-27 14:26:28 +00:00
: param string action : ( ' schedule ' , ' Schedule a call ' ) , ( ' log ' , ' Log a call ' )
2011-10-24 13:30:10 +00:00
"""
phonecall = self . pool . get ( ' crm.phonecall ' )
model_data = self . pool . get ( ' ir.model.data ' )
phonecall_dict = { }
if not categ_id :
res_id = model_data . _get_id ( cr , uid , ' crm ' , ' categ_phone2 ' )
if res_id :
categ_id = model_data . browse ( cr , uid , res_id , context = context ) . res_id
for lead in self . browse ( cr , uid , ids , context = context ) :
if not section_id :
section_id = lead . section_id and lead . section_id . id or False
if not user_id :
user_id = lead . user_id and lead . user_id . id or False
vals = {
2012-12-05 18:35:45 +00:00
' name ' : call_summary ,
' opportunity_id ' : lead . id ,
' user_id ' : user_id or False ,
' categ_id ' : categ_id or False ,
' description ' : desc or ' ' ,
' date ' : schedule_time ,
' section_id ' : section_id or False ,
' partner_id ' : lead . partner_id and lead . partner_id . id or False ,
' partner_phone ' : phone or lead . phone or ( lead . partner_id and lead . partner_id . phone or False ) ,
' partner_mobile ' : lead . partner_id and lead . partner_id . mobile or False ,
' priority ' : lead . priority ,
2011-10-24 13:30:10 +00:00
}
new_id = phonecall . create ( cr , uid , vals , context = context )
2012-03-20 13:22:11 +00:00
phonecall . case_open ( cr , uid , [ new_id ] , context = context )
2011-10-24 13:30:10 +00:00
if action == ' log ' :
2012-03-20 13:22:11 +00:00
phonecall . case_close ( cr , uid , [ new_id ] , context = context )
2011-10-24 13:30:10 +00:00
phonecall_dict [ lead . id ] = new_id
2012-03-20 13:22:11 +00:00
self . schedule_phonecall_send_note ( cr , uid , [ lead . id ] , new_id , action , context = context )
2011-10-24 13:30:10 +00:00
return phonecall_dict
2011-10-21 07:01:11 +00:00
def redirect_opportunity_view ( self , cr , uid , opportunity_id , context = None ) :
models_data = self . pool . get ( ' ir.model.data ' )
2012-11-19 18:12:24 +00:00
# Get opportunity views
2012-12-05 15:50:43 +00:00
dummy , form_view = models_data . get_object_reference ( cr , uid , ' crm ' , ' crm_case_form_view_oppor ' )
dummy , tree_view = models_data . get_object_reference ( cr , uid , ' crm ' , ' crm_case_tree_view_oppor ' )
2011-10-21 07:01:11 +00:00
return {
2012-11-19 18:12:24 +00:00
' name ' : _ ( ' Opportunity ' ) ,
' view_type ' : ' form ' ,
' view_mode ' : ' tree, form ' ,
' res_model ' : ' crm.lead ' ,
' domain ' : [ ( ' type ' , ' = ' , ' opportunity ' ) ] ,
' res_id ' : int ( opportunity_id ) ,
' view_id ' : False ,
2012-12-05 15:50:43 +00:00
' views ' : [ ( form_view or False , ' form ' ) ,
2013-02-05 10:18:22 +00:00
( tree_view or False , ' tree ' ) ,
( False , ' calendar ' ) , ( False , ' graph ' ) ] ,
2012-11-19 18:12:24 +00:00
' type ' : ' ir.actions.act_window ' ,
}
def redirect_lead_view ( self , cr , uid , lead_id , context = None ) :
models_data = self . pool . get ( ' ir.model.data ' )
# Get lead views
2012-12-05 15:50:43 +00:00
dummy , form_view = models_data . get_object_reference ( cr , uid , ' crm ' , ' crm_case_form_view_leads ' )
dummy , tree_view = models_data . get_object_reference ( cr , uid , ' crm ' , ' crm_case_tree_view_leads ' )
2012-11-19 18:12:24 +00:00
return {
' name ' : _ ( ' Lead ' ) ,
' view_type ' : ' form ' ,
' view_mode ' : ' tree, form ' ,
' res_model ' : ' crm.lead ' ,
' domain ' : [ ( ' type ' , ' = ' , ' lead ' ) ] ,
' res_id ' : int ( lead_id ) ,
' view_id ' : False ,
2012-12-05 15:50:43 +00:00
' views ' : [ ( form_view or False , ' form ' ) ,
( tree_view or False , ' tree ' ) ,
2012-11-19 18:12:24 +00:00
( False , ' calendar ' ) , ( False , ' graph ' ) ] ,
' type ' : ' ir.actions.act_window ' ,
2011-10-21 07:01:11 +00:00
}
2011-08-25 04:10:37 +00:00
def action_makeMeeting ( self , cr , uid , ids , context = None ) :
2012-12-05 18:35:45 +00:00
"""
Open meeting ' s calendar view to schedule meeting on current opportunity.
: return dict : dictionary value for created Meeting view
2011-08-25 04:10:37 +00:00
"""
2012-07-06 12:29:59 +00:00
opportunity = self . browse ( cr , uid , ids [ 0 ] , context )
res = self . pool . get ( ' ir.actions.act_window ' ) . for_xml_id ( cr , uid , ' base_calendar ' , ' action_crm_meeting ' , context )
res [ ' context ' ] = {
' default_opportunity_id ' : opportunity . id ,
' default_partner_id ' : opportunity . partner_id and opportunity . partner_id . id or False ,
2012-07-26 12:03:35 +00:00
' default_partner_ids ' : opportunity . partner_id and [ opportunity . partner_id . id ] or False ,
2012-07-06 12:29:59 +00:00
' default_user_id ' : uid ,
' default_section_id ' : opportunity . section_id and opportunity . section_id . id or False ,
' default_email_from ' : opportunity . email_from ,
' default_name ' : opportunity . name ,
}
return res
2011-08-27 21:19:48 +00:00
2011-08-25 04:10:37 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
2012-05-30 09:03:40 +00:00
if vals . get ( ' stage_id ' ) and not vals . get ( ' probability ' ) :
2012-01-30 10:29:39 +00:00
# change probability of lead(s) if required by stage
2012-05-30 09:03:40 +00:00
stage = self . pool . get ( ' crm.case.stage ' ) . browse ( cr , uid , vals [ ' stage_id ' ] , context = context )
if stage . on_change :
2012-01-30 10:29:39 +00:00
vals [ ' probability ' ] = stage . probability
2012-12-19 16:40:48 +00:00
return super ( crm_lead , self ) . write ( cr , uid , ids , vals , context = context )
2012-06-04 10:20:49 +00:00
2012-12-18 13:38:44 +00:00
def new_mail_send ( self , cr , uid , ids , context = None ) :
'''
This function opens a window to compose an email , with the edi sale template message loaded by default
'''
assert len ( ids ) == 1 , ' This option should only be used for a single id at a time. '
ir_model_data = self . pool . get ( ' ir.model.data ' )
try :
template_id = ir_model_data . get_object_reference ( cr , uid , ' crm ' , ' email_template_opportunity_mail ' ) [ 1 ]
except ValueError :
template_id = False
try :
compose_form_id = ir_model_data . get_object_reference ( cr , uid , ' mail ' , ' email_compose_message_wizard_form ' ) [ 1 ]
except ValueError :
2013-02-28 09:16:12 +00:00
compose_form_id = False
2012-12-18 13:38:44 +00:00
if context is None :
context = { }
ctx = context . copy ( )
ctx . update ( {
' default_model ' : ' crm.lead ' ,
' default_res_id ' : ids [ 0 ] ,
' default_use_template ' : bool ( template_id ) ,
' default_template_id ' : template_id ,
' default_composition_mode ' : ' comment ' ,
} )
return {
' type ' : ' ir.actions.act_window ' ,
' view_type ' : ' form ' ,
' view_mode ' : ' form ' ,
' res_model ' : ' mail.compose.message ' ,
' views ' : [ ( compose_form_id , ' form ' ) ] ,
' view_id ' : compose_form_id ,
' target ' : ' new ' ,
' context ' : ctx ,
}
2012-06-04 10:20:49 +00:00
# ----------------------------------------
# Mail Gateway
# ----------------------------------------
2011-10-21 07:01:11 +00:00
2013-01-03 17:03:16 +00:00
def message_get_reply_to ( self , cr , uid , ids , context = None ) :
""" Override to get the reply_to of the parent project. """
return [ lead . section_id . message_get_reply_to ( ) [ 0 ] if lead . section_id else False
for lead in self . browse ( cr , uid , ids , context = context ) ]
2013-02-20 12:49:23 +00:00
def message_get_suggested_recipients ( self , cr , uid , ids , context = None ) :
recipients = super ( crm_lead , self ) . message_get_suggested_recipients ( cr , uid , ids , context = context )
for lead in self . browse ( cr , uid , ids , context = context ) :
if lead . partner_id :
2013-02-28 17:05:46 +00:00
self . _message_add_suggested_recipient ( cr , uid , recipients , lead , partner = lead . partner_id , reason = _ ( ' Customer ' ) )
2013-02-22 10:14:34 +00:00
elif lead . email_from :
2013-02-28 17:05:46 +00:00
self . _message_add_suggested_recipient ( cr , uid , recipients , lead , email = lead . email_from , reason = _ ( ' Customer Email ' ) )
2013-02-20 12:49:23 +00:00
return recipients
2011-07-22 18:23:37 +00:00
def message_new ( self , cr , uid , msg , custom_values = None , context = None ) :
2012-06-04 14:12:54 +00:00
""" Overrides mail_thread message_new that is called by the mailgateway
through message_process .
This override updates the document according to the email .
2012-06-04 10:20:49 +00:00
"""
2013-03-07 15:32:16 +00:00
if custom_values is None :
custom_values = { }
2012-10-09 15:54:20 +00:00
desc = html2plaintext ( msg . get ( ' body ' ) ) if msg . get ( ' body ' ) else ' '
2013-01-10 17:27:23 +00:00
defaults = {
2012-06-04 14:12:54 +00:00
' name ' : msg . get ( ' subject ' ) or _ ( " No Subject " ) ,
2012-10-09 15:54:20 +00:00
' description ' : desc ,
2012-06-04 14:12:54 +00:00
' email_from ' : msg . get ( ' from ' ) ,
2010-06-24 19:53:32 +00:00
' email_cc ' : msg . get ( ' cc ' ) ,
2013-01-15 13:43:59 +00:00
' partner_id ' : msg . get ( ' author_id ' , False ) ,
2010-06-24 19:53:32 +00:00
' user_id ' : False ,
2013-01-10 17:27:23 +00:00
}
2013-01-29 14:38:15 +00:00
if msg . get ( ' author_id ' ) :
defaults . update ( self . on_change_partner ( cr , uid , None , msg . get ( ' author_id ' ) , context = context ) [ ' value ' ] )
2012-06-04 10:20:49 +00:00
if msg . get ( ' priority ' ) in dict ( crm . AVAILABLE_PRIORITIES ) :
2013-01-10 17:27:23 +00:00
defaults [ ' priority ' ] = msg . get ( ' priority ' )
defaults . update ( custom_values )
return super ( crm_lead , self ) . message_new ( cr , uid , msg , custom_values = defaults , context = context )
2010-06-24 19:53:32 +00:00
2012-06-04 14:12:54 +00:00
def message_update ( self , cr , uid , ids , msg , update_vals = None , context = None ) :
""" Overrides mail_thread message_update that is called by the mailgateway
through message_process .
This method updates the document according to the email .
2012-06-04 10:20:49 +00:00
"""
2010-06-24 19:53:32 +00:00
if isinstance ( ids , ( str , int , long ) ) :
ids = [ ids ]
2012-06-04 14:12:54 +00:00
if update_vals is None : update_vals = { }
2011-07-22 18:23:37 +00:00
2010-08-26 15:31:21 +00:00
if msg . get ( ' priority ' ) in dict ( crm . AVAILABLE_PRIORITIES ) :
2012-08-23 20:06:29 +00:00
update_vals [ ' priority ' ] = msg . get ( ' priority ' )
2010-06-24 19:53:32 +00:00
maps = {
' cost ' : ' planned_cost ' ,
' revenue ' : ' planned_revenue ' ,
2012-06-04 14:12:54 +00:00
' probability ' : ' probability ' ,
2010-06-24 19:53:32 +00:00
}
2012-08-17 12:06:06 +00:00
for line in msg . get ( ' body ' , ' ' ) . split ( ' \n ' ) :
2010-06-24 19:53:32 +00:00
line = line . strip ( )
2012-11-19 13:21:41 +00:00
res = tools . command_re . match ( line )
2010-06-25 12:05:00 +00:00
if res and maps . get ( res . group ( 1 ) . lower ( ) ) :
2010-06-24 19:53:32 +00:00
key = maps . get ( res . group ( 1 ) . lower ( ) )
2012-08-23 20:06:29 +00:00
update_vals [ key ] = res . group ( 2 ) . lower ( )
2010-06-24 19:53:32 +00:00
2012-06-04 14:12:54 +00:00
return super ( crm_lead , self ) . message_update ( cr , uid , ids , msg , update_vals = update_vals , context = context )
2011-08-25 04:10:37 +00:00
2012-04-02 17:14:14 +00:00
# ----------------------------------------
# OpenChatter methods and notifications
# ----------------------------------------
2011-08-27 21:19:48 +00:00
2012-04-02 17:14:14 +00:00
def schedule_phonecall_send_note ( self , cr , uid , ids , phonecall_id , action , context = None ) :
phonecall = self . pool . get ( ' crm.phonecall ' ) . browse ( cr , uid , [ phonecall_id ] , context = context ) [ 0 ]
2013-03-06 14:01:07 +00:00
if action == ' log ' :
prefix = ' Logged '
else :
prefix = ' Scheduled '
suffix = ' %s ' % phonecall . description
message = _ ( " %s a call for %s . %s " ) % ( prefix , phonecall . date , suffix )
2012-08-17 10:03:02 +00:00
return self . message_post ( cr , uid , ids , body = message , context = context )
2012-04-02 17:14:14 +00:00
2012-10-25 09:42:31 +00:00
def onchange_state ( self , cr , uid , ids , state_id , context = None ) :
2012-10-25 10:06:29 +00:00
if state_id :
2012-10-25 10:26:36 +00:00
country_id = self . pool . get ( ' res.country.state ' ) . browse ( cr , uid , state_id , context ) . country_id . id
2012-11-02 09:13:25 +00:00
return { ' value ' : { ' country_id ' : country_id } }
return { }
2010-03-22 10:40:26 +00:00
2010-03-23 14:12:01 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: