X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fcrm%2Fcrm_lead.py;h=a5315ab3a529b1316efd2fab85b51880e51b7b1e;hb=0c36f8a4cecebe22c7d37a9cc3920910cde35fc9;hp=046d44198095fe9dcf784b9448d8f4729f4727d9;hpb=bbeb6e60aae5fa224f7346a4c58b515274ab023b;p=odoo%2Fodoo.git diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 046d441..a5315ab 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -23,6 +23,7 @@ import crm from datetime import datetime from operator import itemgetter +import openerp from openerp import SUPERUSER_ID from openerp import tools from openerp.addons.base.res.res_partner import format_address @@ -71,10 +72,11 @@ class crm_lead(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.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.on_change, - '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, }, } @@ -92,7 +94,7 @@ class crm_lead(format_address, osv.osv): 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, [('sequence', '=', '1')], 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' @@ -151,6 +153,11 @@ class crm_lead(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) @@ -239,10 +246,10 @@ class crm_lead(format_address, osv.osv): '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), @@ -258,8 +265,8 @@ class crm_lead(format_address, osv.osv): # 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), @@ -311,7 +318,10 @@ class crm_lead(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_id(self, cr, uid, ids, partner_id, context=None): values = {} @@ -328,6 +338,7 @@ class crm_lead(format_address, osv.osv): 'phone': partner.phone, 'mobile': partner.mobile, 'fax': partner.fax, + 'zip': partner.zip, } return {'value': values} @@ -381,34 +392,52 @@ class crm_lead(format_address, osv.osv): # 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: state=cancel and probability=0 """ - 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: - return self.write(cr, uid, [lead.id], {'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 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: state=done and probability=100 """ - 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), ('sequence', '>', 1)], 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: - return self.write(cr, uid, [lead.id], {'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 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 case_escalate(self, cr, uid, ids, context=None): """ Escalates case to parent level """ @@ -616,7 +645,7 @@ class crm_lead(format_address, osv.osv): # 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.stage_id.probability != 0 or 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)) @@ -677,7 +706,7 @@ class crm_lead(format_address, osv.osv): '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) + 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): @@ -687,7 +716,7 @@ class crm_lead(format_address, osv.osv): customer = partner.browse(cr, uid, partner_id, context=context) for lead in self.browse(cr, uid, ids, context=context): # TDE: was if lead.state in ('done', 'cancel'): - if (lead.probability == '100') or (lead.probability == '0' and lead.stage_id.sequence != '1'): + 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) @@ -741,24 +770,6 @@ class crm_lead(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. @@ -772,13 +783,16 @@ class crm_lead(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 @@ -817,9 +831,11 @@ class crm_lead(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 @@ -892,7 +908,7 @@ class crm_lead(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, @@ -926,6 +942,23 @@ class crm_lead(format_address, osv.osv): vals.update(onchange_stage_values) return super(crm_lead, self).write(cr, uid, ids, vals, context=context) + def copy(self, cr, uid, id, default=None, context=None): + if not default: + default = {} + if not context: + context = {} + 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 # ---------------------------------------- @@ -1010,11 +1043,13 @@ class crm_lead(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):