X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fcrm%2Fcrm_lead.py;h=a5315ab3a529b1316efd2fab85b51880e51b7b1e;hb=0c36f8a4cecebe22c7d37a9cc3920910cde35fc9;hp=aa989f3a39353debbd2bb6649f9755e9d8f68c17;hpb=f14eddbbe9ed8a94078a7ca126ac9ae32a880234;p=odoo%2Fodoo.git diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index aa989f3..a5315ab 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -19,18 +19,17 @@ # ############################################################################## -from openerp.addons.base_status.base_stage import base_stage import crm from datetime import datetime from operator import itemgetter -from openerp.osv import fields, osv, orm -import time + +import openerp from openerp import SUPERUSER_ID from openerp import tools -from openerp.tools.translate import _ -from openerp.tools import html2plaintext - from openerp.addons.base.res.res_partner import format_address +from openerp.osv import fields, osv, orm +from openerp.tools import html2plaintext +from openerp.tools.translate import _ CRM_LEAD_FIELDS_TO_MERGE = ['name', 'partner_id', @@ -64,7 +63,7 @@ CRM_LEAD_FIELDS_TO_MERGE = ['name', 'partner_name'] -class crm_lead(base_stage, format_address, osv.osv): +class crm_lead(format_address, osv.osv): """ CRM Lead Case """ _name = "crm.lead" _description = "Lead/Opportunity" @@ -73,11 +72,12 @@ class crm_lead(base_stage, format_address, osv.osv): _track = { 'stage_id': { - 'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.sequence == 1, - 'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: obj.probability > 0 and obj.probability < 100, - 'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj.probability == 100, - 'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.sequence != 1, - } + # this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages + 'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.sequence <= 1, + 'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: (obj.stage_id and obj.stage_id.sequence > 1) and obj.probability < 100, + 'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj.probability == 100 and obj.stage_id and obj.stage_id.fold, + 'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.fold and obj.stage_id.sequence > 1, + }, } def get_empty_list_help(self, cr, uid, help, context=None): @@ -87,24 +87,15 @@ class crm_lead(base_stage, format_address, osv.osv): context['empty_list_help_document_name'] = _("leads") return super(crm_lead, self).get_empty_list_help(cr, uid, help, context=context) - def create(self, cr, uid, vals, context=None): - if context is None: - context = {} - if not vals.get('stage_id'): - ctx = context.copy() - if vals.get('section_id'): - ctx['default_section_id'] = vals['section_id'] - if vals.get('type'): - ctx['default_type'] = vals['type'] - vals['stage_id'] = self._get_default_stage_id(cr, uid, context=ctx) - # context: no_log, because subtype already handle this - create_context = dict(context, mail_create_nolog=True) - return super(crm_lead, self).create(cr, uid, vals, context=create_context) - def _get_default_section_id(self, cr, uid, context=None): """ Gives default section by checking if present in the context """ return self._resolve_section_id_from_context(cr, uid, context=context) or False + 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) + return self.stage_find(cr, uid, [], section_id, [('fold', '=', False)], context=context) + def _resolve_section_id_from_context(self, cr, uid, context=None): """ Returns ID of section based on the value of 'section_id' context key, or None if it cannot be resolved to a single @@ -115,8 +106,7 @@ class crm_lead(base_stage, format_address, osv.osv): 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'] - section_ids = self.pool.get('crm.case.section').name_search(cr, uid, name=section_name, context=context) + section_ids = self.pool.get('crm.case.section').name_search(cr, uid, name=context['default_section_id'], context=context) if len(section_ids) == 1: return int(section_ids[0][0]) return None @@ -163,6 +153,11 @@ class crm_lead(base_stage, format_address, osv.osv): return result, fold def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): + if view_type == 'form' and context and context.get('opportunity_id'): + # TODO: replace by get_formview_action call + lead_type = self.browse(cr, user, context['opportunity_id'], context=context).type + view_lead_xml_id = 'crm_case_form_view_oppor' if lead_type == 'opportunity' else 'crm_case_form_view_leads' + _, view_id = self.pool['ir.model.data'].get_object_reference(cr, user, 'crm', view_lead_xml_id) 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) @@ -238,7 +233,7 @@ class crm_lead(base_stage, format_address, osv.osv): 'section_id': fields.many2one('crm.case.section', 'Sales Team', select=True, track_visibility='onchange', help='When sending mails, the default email address is taken from the sales team.'), 'create_date': fields.datetime('Creation Date', readonly=True), - '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"), + 'email_cc': fields.text('Global CC', 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"), 'description': fields.text('Notes'), 'write_date': fields.datetime('Update Date', readonly=True), 'categ_ids': fields.many2many('crm.case.categ', 'crm_lead_category_rel', 'lead_id', 'category_id', 'Categories', \ @@ -247,27 +242,31 @@ class crm_lead(base_stage, format_address, osv.osv): domain="['|',('section_id','=',section_id),('section_id','=',False)]", help="From which campaign (seminar, marketing campaign, mass mailing, ...) did this contact come from?"), 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel (mail, direct, phone, ...)"), 'contact_name': fields.char('Contact Name', size=64), - '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), + '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), '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. " "Filter 'Available for Mass Mailing' allows users to filter the leads when performing mass mailing."), - 'type': fields.selection([('lead', 'Lead'), ('opportunity', 'Opportunity'), ], 'Type', help="Type is used to separate Leads and Opportunities"), + 'type': fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', select=True, help="Type is used to separate Leads and Opportunities"), 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True), 'date_closed': fields.datetime('Closed', readonly=True), - 'stage_id': fields.many2one('crm.case.stage', 'Stage', track_visibility='onchange',), + 'stage_id': fields.many2one('crm.case.stage', 'Stage', track_visibility='onchange', select=True, + domain="['&', ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"), 'user_id': fields.many2one('res.users', 'Salesperson', select=True, track_visibility='onchange'), 'referred': fields.char('Referred By', size=64), - 'date_open': fields.datetime('Opened', readonly=True), + 'date_open': fields.datetime('Assigned', readonly=True), 'day_open': fields.function(_compute_day, string='Days to Open', \ multi='day_open', type="float", store=True), 'day_close': fields.function(_compute_day, string='Days to Close', \ multi='day_close', type="float", store=True), + 'date_last_stage_update': fields.datetime('Last Stage Update', select=True), + # Messaging and marketing + 'message_bounce': fields.integer('Bounce'), # Only used for type opportunity 'probability': fields.float('Success Rate (%)', group_operator="avg"), 'planned_revenue': fields.float('Expected Revenue', track_visibility='always'), - 'ref': fields.reference('Reference', selection=crm._links_get, size=128), - 'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128), + 'ref': fields.reference('Reference', selection=openerp.addons.base.res.res_request.referencable_models), + 'ref2': fields.reference('Reference 2', selection=openerp.addons.base.res.res_request.referencable_models), 'phone': fields.char("Phone", size=64), 'date_deadline': fields.date('Expected Closing', help="Estimate of the date on which the opportunity will be won."), 'date_action': fields.date('Next Action Date', select=True), @@ -300,13 +299,13 @@ class crm_lead(base_stage, format_address, osv.osv): _defaults = { 'active': 1, 'type': 'lead', - '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), + 'user_id': lambda s, cr, uid, c: uid, '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), '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], 'color': 0, + 'date_last_stage_update': fields.datetime.now, } _sql_constraints = [ @@ -319,26 +318,29 @@ class crm_lead(base_stage, format_address, osv.osv): stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context=context) if not stage.on_change: return {'value': {}} - return {'value': {'probability': stage.probability}} + vals = {'probability': stage.probability} + if stage.probability >= 100 or (stage.probability == 0 and stage.sequence > 1): + vals['date_closed'] = fields.datetime.now() + return {'value': vals} - def on_change_partner(self, cr, uid, ids, partner_id, context=None): - result = {} + def on_change_partner_id(self, cr, uid, ids, partner_id, context=None): values = {} if partner_id: partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) values = { - 'partner_name' : partner.name, - '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, - 'email_from' : partner.email, - 'phone' : partner.phone, - 'mobile' : partner.mobile, - 'fax' : partner.fax, + 'partner_name': partner.name, + '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, + 'email_from': partner.email, + 'phone': partner.phone, + 'mobile': partner.mobile, + 'fax': partner.fax, + 'zip': partner.zip, } - return {'value' : values} + return {'value': values} def on_change_user(self, cr, uid, ids, user_id, context=None): """ When changing the user, also set a section_id or restrict section id @@ -350,22 +352,6 @@ class crm_lead(base_stage, format_address, osv.osv): section_id = section_ids[0] return {'value': {'section_id': section_id}} - 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 won and lost cases. - """ - 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 probability not in (0,100)', - (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) - def stage_find(self, cr, uid, cases, section_id, domain=None, order='sequence', context=None): """ Override of the base.stage method Parameter of the stage search taken from the lead: @@ -376,10 +362,14 @@ class crm_lead(base_stage, format_address, osv.osv): """ if isinstance(cases, (int, long)): cases = self.browse(cr, uid, cases, context=context) + if context is None: + context = {} + # check whether we should try to add a condition on type + avoid_add_type_term = any([term for term in domain if len(term) == 3 if term[0] == 'type']) # collect all section_ids section_ids = set() types = ['both'] - if not cases: + if not cases and context.get('default_type'): ctx_type = context.get('default_type') types += [ctx_type] if section_id: @@ -397,55 +387,86 @@ class crm_lead(base_stage, format_address, osv.osv): search_domain.append(('section_ids', '=', section_id)) search_domain.append(('case_default', '=', True)) # AND with cases types - search_domain.append(('type', 'in', types)) + if not avoid_add_type_term: + 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) + stage_ids = self.pool.get('crm.case.stage').search(cr, uid, search_domain, order=order, limit=1, context=context) if stage_ids: return stage_ids[0] return False def case_mark_lost(self, cr, uid, ids, context=None): - """ Mark the case as lost: stage with probability=0, on_change=True """ - for lead in self.browse(cr, uid, ids): - stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0), ('on_change', '=', True), ('sequence', '>', 1)], context=context) + """ Mark the case as lost: state=cancel and probability=0 + :deprecated: this method will be removed in OpenERP v8. + """ + stages_leads = {} + for lead in self.browse(cr, uid, ids, context=context): + stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0), ('fold', '=', True), ('sequence', '>', 1)], context=context) if stage_id: - self.case_set(cr, uid, [lead.id], new_stage_id=stage_id, context=context) + if stages_leads.get(stage_id): + stages_leads[stage_id].append(lead.id) + else: + stages_leads[stage_id] = [lead.id] else: - raise self.pool.get('res.config.settings').get_config_warning(cr, - _("To relieve your sales pipe and group all Lost opportunities, configure one of your sales stage as follow:\n" - "probability = 0, sequence != 1 and on_change = True.\n" - "You can create a specific column or edit an existing one from the menu %(menu:crm.menu_crm_case_section_act)s"), context=context) + raise osv.except_osv(_('Warning!'), + _('To relieve your sales pipe and group all Lost opportunities, configure one of your sales stage as follow:\n' + 'probability = 0 %, select "Change Probability Automatically".\n' + 'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.')) + for stage_id, lead_ids in stages_leads.items(): + self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context) return True def case_mark_won(self, cr, uid, ids, context=None): - """ Mark the case as won: stage with probability=100, , on_change=True """ - for lead in self.browse(cr, uid, ids): - stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0), ('on_change', '=', True)], context=context) + """ Mark the case as won: state=done and probability=100 + :deprecated: this method will be removed in OpenERP v8. + """ + stages_leads = {} + for lead in self.browse(cr, uid, ids, context=context): + stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0), ('fold', '=', True)], context=context) if stage_id: - self.case_set(cr, uid, [lead.id], new_stage_id=stage_id, context=context) + if stages_leads.get(stage_id): + stages_leads[stage_id].append(lead.id) + else: + stages_leads[stage_id] = [lead.id] else: - raise self.pool.get('res.config.settings').get_config_warning(cr, - _("To relieve your sales pipe and group all Won opportunities, configure one of your sales stage as follow:\n" - "probability = 100 and on_change = True.\n" - "You can create a specific column or edit an existing one from the menu %(menu:crm.menu_crm_case_section_act)s"), context=context) + raise osv.except_osv(_('Warning!'), + _('To relieve your sales pipe and group all Won opportunities, configure one of your sales stage as follow:\n' + 'probability = 100 % and select "Change Probability Automatically".\n' + 'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.')) + for stage_id, lead_ids in stages_leads.items(): + self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context) return True - def set_priority(self, cr, uid, ids, priority): + def case_escalate(self, cr, uid, ids, context=None): + """ Escalates case to parent level """ + for case in self.browse(cr, uid, ids, context=context): + data = {'active': True} + if case.section_id.parent_id: + data['section_id'] = case.section_id.parent_id.id + if case.section_id.parent_id.change_responsible: + if case.section_id.parent_id.user_id: + data['user_id'] = case.section_id.parent_id.user_id.id + else: + raise osv.except_osv(_('Error!'), _("You are already at the top level of your sales-team category.\nTherefore you cannot escalate furthermore.")) + self.write(cr, uid, [case.id], data, context=context) + return True + + def set_priority(self, cr, uid, ids, priority, context=None): """ Set lead priority """ - return self.write(cr, uid, ids, {'priority': priority}) + return self.write(cr, uid, ids, {'priority': priority}, context=context) def set_high_priority(self, cr, uid, ids, context=None): """ Set lead priority to high """ - return self.set_priority(cr, uid, ids, '1') + return self.set_priority(cr, uid, ids, '1', context=context) def set_normal_priority(self, cr, uid, ids, context=None): """ Set lead priority to normal """ - return self.set_priority(cr, uid, ids, '3') + return self.set_priority(cr, uid, ids, '3', context=context) def _merge_get_result_type(self, cr, uid, opps, context=None): """ @@ -602,7 +623,7 @@ class crm_lead(base_stage, format_address, osv.osv): attachment.write(values) return True - def merge_opportunity(self, cr, uid, ids, context=None): + def merge_opportunity(self, cr, uid, ids, user_id=False, section_id=False, context=None): """ Different cases of merge: - merge leads together = 1 new lead @@ -619,9 +640,12 @@ class crm_lead(base_stage, format_address, osv.osv): opportunities = self.browse(cr, uid, ids, context=context) sequenced_opps = [] + # Sorting the leads/opps according to the confidence level of its stage, which relates to the probability of winning it + # The confidence level increases with the stage sequence, except when the stage probability is 0.0 (Lost cases) + # An Opportunity always has higher confidence level than a lead, unless its stage probability is 0.0 for opportunity in opportunities: sequence = -1 - if opportunity.stage_id and (opportunity.probability == 0 and opportunity.stage_id and opportunity.stage_id.sequence != 1): + if opportunity.stage_id and not opportunity.stage_id.fold: sequence = opportunity.stage_id.sequence sequenced_opps.append(((int(sequence != -1 and opportunity.type == 'opportunity'), sequence, -opportunity.id), opportunity)) @@ -636,6 +660,11 @@ class crm_lead(base_stage, format_address, osv.osv): fields = list(CRM_LEAD_FIELDS_TO_MERGE) merged_data = self._merge_data(cr, uid, ids, highest, fields, context=context) + if user_id: + merged_data['user_id'] = user_id + if section_id: + merged_data['section_id'] = section_id + # Merge messages and attachements into the first opportunity self._merge_opportunity_history(cr, uid, highest.id, tail_opportunities, context=context) self._merge_opportunity_attachments(cr, uid, highest.id, tail_opportunities, context=context) @@ -643,7 +672,7 @@ class crm_lead(base_stage, format_address, osv.osv): # Merge notifications about loss of information opportunities = [highest] opportunities.extend(opportunities_rest) - self._merge_notify(cr, uid, highest, opportunities, context=context) + self._merge_notify(cr, uid, highest.id, opportunities, context=context) # 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('section_id'): section_stage_ids = self.pool.get('crm.case.stage').search(cr, uid, [('section_ids', 'in', merged_data['section_id']), ('type', '=', merged_data.get('type'))], order='sequence', context=context) @@ -676,8 +705,8 @@ class crm_lead(base_stage, format_address, osv.osv): 'email_from': customer and customer.email or lead.email_from, 'phone': customer and customer.phone or lead.phone, } - if not lead.stage_id or lead.stage_id.type == 'lead': - val['stage_id'] = self.stage_find(cr, uid, [lead], section_id, ['&', ('sequence', '=', '1'), ('type', 'in', ('opportunity', 'both'))], context=context) + if not lead.stage_id or lead.stage_id.type=='lead': + val['stage_id'] = self.stage_find(cr, uid, [lead], section_id, [('type', 'in', ('opportunity', 'both'))], context=context) return val def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, section_id=False, context=None): @@ -686,12 +715,11 @@ class crm_lead(base_stage, format_address, osv.osv): partner = self.pool.get('res.partner') customer = partner.browse(cr, uid, partner_id, context=context) for lead in self.browse(cr, uid, ids, context=context): - # avoid done / cancelled leads - if lead.probability == 100 or (lead.probability == 0 and lead.stage_id and lead.stage_id.sequence == 1): + # TDE: was if lead.state in ('done', 'cancel'): + if lead.probability == 100 or (lead.probability == 0 and lead.stage_id.fold): continue vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context) self.write(cr, uid, [lead.id], vals, context=context) - self.message_post(cr, uid, ids, body=_("Lead converted into an Opportunity"), subtype="crm.mt_lead_convert_to_opportunity", context=context) if user_ids or section_id: self.allocate_salesman(cr, uid, ids, user_ids, section_id, context=context) @@ -742,24 +770,6 @@ class crm_lead(base_stage, format_address, osv.osv): ) return partner_id - def _lead_set_partner(self, cr, uid, lead, partner_id, context=None): - """ - 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 - """ - res = False - res_partner = self.pool.get('res.partner') - if partner_id: - res_partner.write(cr, uid, partner_id, {'section_id': lead.section_id and lead.section_id.id or False}) - contact_id = res_partner.address_get(cr, uid, [partner_id])['default'] - res = lead.write({'partner_id': partner_id}, context=context) - message = _("Partner set to %s." % (lead.partner_id.name)) - self.message_post(cr, uid, [lead.id], body=message, context=context) - return res - def handle_partner_assignation(self, cr, uid, ids, action='create', partner_id=False, context=None): """ Handle partner assignation during a lead conversion. @@ -773,13 +783,16 @@ class crm_lead(base_stage, format_address, osv.osv): """ #TODO this is a duplication of the handle_partner_assignation method of crm_phonecall partner_ids = {} - # If a partner_id is given, force this partner for all elements - force_partner_id = partner_id for lead in self.browse(cr, uid, ids, context=context): # If the action is set to 'create' and no partner_id is set, create a new one - if action == 'create': - partner_id = force_partner_id or self._create_lead_partner(cr, uid, lead, context) - self._lead_set_partner(cr, uid, lead, partner_id, context=context) + if lead.partner_id: + partner_ids[lead.id] = lead.partner_id.id + continue + if not partner_id and action == 'create': + partner_id = self._create_lead_partner(cr, uid, lead, context) + self.pool['res.partner'].write(cr, uid, partner_id, {'section_id': lead.section_id and lead.section_id.id or False}) + if partner_id: + lead.write({'partner_id': partner_id}, context=context) partner_ids[lead.id] = partner_id return partner_ids @@ -818,9 +831,11 @@ class crm_lead(base_stage, format_address, osv.osv): 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: + try: + res_id = model_data._get_id(cr, uid, 'crm', 'categ_phone2') categ_id = model_data.browse(cr, uid, res_id, context=context).res_id + except ValueError: + pass 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 @@ -840,9 +855,9 @@ class crm_lead(base_stage, format_address, osv.osv): 'priority': lead.priority, } new_id = phonecall.create(cr, uid, vals, context=context) - phonecall.case_open(cr, uid, [new_id], context=context) + phonecall.write(cr, uid, [new_id], {'state': 'open'}, context=context) if action == 'log': - phonecall.case_close(cr, uid, [new_id], context=context) + phonecall.write(cr, uid, [new_id], {'state': 'done'}, context=context) phonecall_dict[lead.id] = new_id self.schedule_phonecall_send_note(cr, uid, [lead.id], new_id, action, context=context) return phonecall_dict @@ -893,7 +908,7 @@ class crm_lead(base_stage, format_address, osv.osv): :return dict: dictionary value for created Meeting view """ 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 = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'calendar', 'action_calendar_event', context) res['context'] = { 'default_opportunity_id': opportunity.id, 'default_partner_id': opportunity.partner_id and opportunity.partner_id.id or False, @@ -905,52 +920,44 @@ class crm_lead(base_stage, format_address, osv.osv): } return res + def create(self, cr, uid, vals, context=None): + if context is None: + context = {} + if vals.get('type') and not context.get('default_type'): + context['default_type'] = vals.get('type') + if vals.get('section_id') and not context.get('default_section_id'): + context['default_section_id'] = vals.get('section_id') + + # context: no_log, because subtype already handle this + create_context = dict(context, mail_create_nolog=True) + return super(crm_lead, self).create(cr, uid, vals, context=create_context) + def write(self, cr, uid, ids, vals, context=None): - stage_pool=self.pool.get('crm.case.stage') + # stage change: update date_last_stage_update + if 'stage_id' in vals: + vals['date_last_stage_update'] = fields.datetime.now() + # stage change with new stage: update probability if vals.get('stage_id') and not vals.get('probability'): - # change probability of lead(s) if required by stage - stage = stage_pool.browse(cr, uid, vals['stage_id'], context=context) - if stage.on_change: - vals['probability'] = stage.probability - if vals.get('probability') == 100: - vals['stage_id'] = stage_pool.search(cr, uid, [('probability','=',100.0)],order='sequence')[0] + onchange_stage_values = self.onchange_stage_id(cr, uid, ids, vals.get('stage_id'), context=context)['value'] + vals.update(onchange_stage_values) return super(crm_lead, self).write(cr, uid, ids, vals, context=context) - 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: - compose_form_id = False - if context is None: + def copy(self, cr, uid, id, default=None, context=None): + if not default: + default = {} + if not context: 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 { - 'name': _('Compose Email'), - '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, - } + lead = self.browse(cr, uid, id, context=context) + local_context = dict(context) + local_context.setdefault('default_type', lead.type) + local_context.setdefault('default_section_id', lead.section_id) + if lead.type == 'opportunity': + default['date_open'] = fields.datetime.now() + else: + default['date_open'] = False + default['date_closed'] = False + default['stage_id'] = self._get_default_stage_id(cr, uid, local_context) + return super(crm_lead, self).copy(cr, uid, id, default, context=context) # ---------------------------------------- # Mail Gateway @@ -990,17 +997,15 @@ class crm_lead(base_stage, format_address, osv.osv): """ if custom_values is None: custom_values = {} - desc = html2plaintext(msg.get('body')) if msg.get('body') else '' defaults = { 'name': msg.get('subject') or _("No Subject"), - 'description': desc, 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), 'partner_id': msg.get('author_id', False), 'user_id': False, } if msg.get('author_id'): - defaults.update(self.onchange_partner_id(cr, uid, None, msg.get('author_id'), context=context)['value']) + defaults.update(self.on_change_partner_id(cr, uid, None, msg.get('author_id'), context=context)['value']) if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES): defaults['priority'] = msg.get('priority') defaults.update(custom_values) @@ -1013,14 +1018,14 @@ class crm_lead(base_stage, format_address, osv.osv): """ if isinstance(ids, (str, int, long)): ids = [ids] - if update_vals is None: - update_vals = {} + if update_vals is None: update_vals = {} + if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES): update_vals['priority'] = msg.get('priority') maps = { - 'cost': 'planned_cost', + 'cost':'planned_cost', 'revenue': 'planned_revenue', - 'probability': 'probability', + 'probability':'probability', } for line in msg.get('body', '').split('\n'): line = line.strip() @@ -1038,11 +1043,13 @@ class crm_lead(base_stage, format_address, osv.osv): 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] if action == 'log': - prefix = 'Logged' + message = _('Logged a call for %(date)s. %(description)s') else: - prefix = 'Scheduled' - suffix = ' %s' % phonecall.description - message = _("%s a call for %s.%s") % (prefix, phonecall.date, suffix) + message = _('Scheduled a call for %(date)s. %(description)s') + phonecall_date = datetime.strptime(phonecall.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + phonecall_usertime = fields.datetime.context_timestamp(cr, uid, phonecall_date, context=context).strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) + html_time = "" % (phonecall.date, phonecall_usertime) + message = message % dict(date=html_time, description=phonecall.description) return self.message_post(cr, uid, ids, body=message, context=context) def log_meeting(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None):