diff --git a/addons/base_status/__init__.py b/addons/base_status/__init__.py
index e4107f90a37..ea9d738e614 100644
--- a/addons/base_status/__init__.py
+++ b/addons/base_status/__init__.py
@@ -20,6 +20,5 @@
##############################################################################
import base_state
-import base_stage
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/base_status/__openerp__.py b/addons/base_status/__openerp__.py
index 79b6dd2016c..b3936ec10cb 100644
--- a/addons/base_status/__openerp__.py
+++ b/addons/base_status/__openerp__.py
@@ -24,11 +24,10 @@
'version': '1.0',
'category': 'Hidden',
'description': """
-This module handles state and stage. It is derived from the crm_base and crm_case classes from crm.
-===================================================================================================
+This module handles state. It is derived from the crm_base and crm_case classes from crm.
+==========================================================================================
* ``base_state``: state management
- * ``base_stage``: stage management
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
diff --git a/addons/base_status/base_stage.py b/addons/base_status/base_stage.py
deleted file mode 100644
index edf9db166af..00000000000
--- a/addons/base_status/base_stage.py
+++ /dev/null
@@ -1,264 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-today OpenERP SA ()
-#
-# 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 .
-#
-##############################################################################
-
-from openerp.osv import fields, osv
-from openerp.tools.translate import _
-
-class base_stage(object):
- """ Base utility mixin class for objects willing to manage their stages.
- Object that inherit from this class should inherit from mailgate.thread
- to have access to the mail gateway, as well as Chatter. Objects
- subclassing this class should define the following colums:
- - ``date_open`` (datetime field)
- - ``date_closed`` (datetime field)
- - ``user_id`` (many2one to res.users)
- - ``partner_id`` (many2one to res.partner)
- - ``stage_id`` (many2one to a stage definition model)
- - ``state`` (selection field, related to the stage_id.state)
- """
-
- def _get_default_partner(self, cr, uid, context=None):
- """ Gives id of partner for current user
- :param context: if portal not in context returns False
- """
- if context is None:
- context = {}
- if context.get('portal'):
- user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
- return user.partner_id.id
- return False
-
- def _get_default_email(self, cr, uid, context=None):
- """ Gives default email address for current user
- :param context: if portal not in context returns False
- """
- if context is None:
- context = {}
- if context.get('portal'):
- user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
- return user.email
- return False
-
- def _get_default_user(self, cr, uid, context=None):
- """ Gives current user id
- :param context: if portal not in context returns False
- """
- if context is None:
- context = {}
- if not context or context.get('portal'):
- return False
- return uid
-
- def onchange_partner_address_id(self, cr, uid, ids, add, email=False, context=None):
- """ This function returns value of partner email based on Partner Address
- :param add: Id of Partner's address
- :param email: Partner's email ID
- """
- data = {'value': {'email_from': False, 'phone':False}}
- if add:
- address = self.pool.get('res.partner').browse(cr, uid, add)
- data['value'] = {'partner_name': address and address.name or False,
- 'email_from': address and address.email or False,
- 'phone': address and address.phone or False,
- 'street': address and address.street or False,
- 'street2': address and address.street2 or False,
- 'city': address and address.city or False,
- 'state_id': address.state_id and address.state_id.id or False,
- 'zip': address and address.zip or False,
- 'country_id': address.country_id and address.country_id.id or False,
- }
- fields = self.fields_get(cr, uid, context=context or {})
- for key in data['value'].keys():
- if key not in fields:
- del data['value'][key]
- return data
-
- def onchange_partner_id(self, cr, uid, ids, part, email=False):
- """ This function returns value of partner address based on partner
- :param part: Partner's id
- :param email: Partner's email ID
- """
- data={}
- if part:
- addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
- data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
- return {'value': data}
-
- def _get_default_section_id(self, cr, uid, context=None):
- """ Gives default section """
- return False
-
- def _get_default_stage_id(self, cr, uid, context=None):
- """ Gives default stage_id """
- return self.stage_find(cr, uid, [], None, [('state', '=', 'draft')], context=context)
-
- def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None):
- """ Find stage, with a given (optional) domain on the search,
- ordered by the order parameter. If several stages match the
- search criterions, the first one will be returned, according
- to the requested search order.
- This method is meant to be overriden by subclasses. That way
- specific behaviors can be achieved for every class inheriting
- from base_stage.
-
- :param cases: browse_record of cases
- :param section_id: section limitating the search, given for
- a generic search (for example default search).
- A section models concepts such as Sales team
- (for CRM), ou departments (for HR).
- :param domain: a domain on the search of stages
- :param order: order of the search
- """
- return False
-
- def stage_set_with_state_name(self, cr, uid, cases, state_name, context=None):
- """ Set a new stage, with a state_name instead of a stage_id
- :param cases: browse_record of cases
- """
- if isinstance(cases, (int, long)):
- cases = self.browse(cr, uid, cases, context=context)
- for case in cases:
- stage_id = self.stage_find(cr, uid, [case], None, [('state', '=', state_name)], context=context)
- if stage_id:
- self.stage_set(cr, uid, [case.id], stage_id, context=context)
- return True
-
- def stage_set(self, cr, uid, ids, stage_id, context=None):
- """ Set the new stage. This methods is the right method to call
- when changing states. It also checks whether an onchange is
- defined, and execute it.
- """
- value = {}
- if hasattr(self, 'onchange_stage_id'):
- value = self.onchange_stage_id(cr, uid, ids, stage_id, context=context)['value']
- value['stage_id'] = stage_id
- return self.write(cr, uid, ids, value, context=context)
-
- def stage_change(self, cr, uid, ids, op, order, context=None):
- """ Change the stage and take the next one, based on a condition
- writen for the 'sequence' field and an operator. This methods
- checks whether the case has a current stage, and takes its
- sequence. Otherwise, a default 0 sequence is chosen and this
- method will therefore choose the first available stage.
- For example if op is '>' and current stage has a sequence of
- 10, this will call stage_find, with [('sequence', '>', '10')].
- """
- for case in self.browse(cr, uid, ids, context=context):
- seq = 0
- if case.stage_id:
- seq = case.stage_id.sequence or 0
- section_id = None
- next_stage_id = self.stage_find(cr, uid, [case], None, [('sequence', op, seq)],order, context=context)
- if next_stage_id:
- return self.stage_set(cr, uid, [case.id], next_stage_id, context=context)
- return False
-
- def stage_next(self, cr, uid, ids, context=None):
- """ This function computes next stage for case from its current stage
- using available stage for that case type
- """
- return self.stage_change(cr, uid, ids, '>','sequence', context)
-
- def stage_previous(self, cr, uid, ids, context=None):
- """ This function computes previous stage for case from its current
- stage using available stage for that case type
- """
- return self.stage_change(cr, uid, ids, '<', 'sequence desc', context)
-
- def copy(self, cr, uid, id, default=None, context=None):
- """ Overrides orm copy method to avoid copying messages,
- as well as date_closed and date_open columns if they
- exist."""
- if default is None:
- default = {}
-
- if hasattr(self, '_columns'):
- if self._columns.get('date_closed'):
- default.update({ 'date_closed': False, })
- if self._columns.get('date_open'):
- default.update({ 'date_open': False })
- return super(base_stage, self).copy(cr, uid, id, default, context=context)
-
- 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 case_open(self, cr, uid, ids, context=None):
- """ Opens case """
- cases = self.browse(cr, uid, ids, context=context)
- for case in cases:
- data = {'active': True}
- if not case.user_id:
- data['user_id'] = uid
- self.case_set(cr, uid, [case.id], 'open', data, context=context)
- return True
-
- def case_close(self, cr, uid, ids, context=None):
- """ Closes case """
- return self.case_set(cr, uid, ids, 'done', {'active': True, 'date_closed': fields.datetime.now()}, context=context)
-
- def case_cancel(self, cr, uid, ids, context=None):
- """ Cancels case """
- return self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
-
- def case_pending(self, cr, uid, ids, context=None):
- """ Set case as pending """
- return self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context)
-
- def case_reset(self, cr, uid, ids, context=None):
- """ Resets case as draft """
- return self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context)
-
- def case_set(self, cr, uid, ids, new_state_name=None, values_to_update=None, new_stage_id=None, context=None):
- """ Generic method for setting case. This methods wraps the update
- of the record.
-
- :params new_state_name: the new state of the record; this method
- will call ``stage_set_with_state_name``
- that will find the stage matching the
- new state, using the ``stage_find`` method.
- :params new_stage_id: alternatively, you may directly give the
- new stage of the record
- :params state_name: the new value of the state, such as
- 'draft' or 'close'.
- :params update_values: values that will be added with the state
- update when writing values to the record.
- """
- cases = self.browse(cr, uid, ids, context=context)
- # 1. update the stage
- if new_state_name:
- self.stage_set_with_state_name(cr, uid, cases, new_state_name, context=context)
- elif not (new_stage_id is None):
- self.stage_set(cr, uid, ids, new_stage_id, context=context)
- # 2. update values
- if values_to_update:
- self.write(cr, uid, ids, values_to_update, context=context)
- return True
diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py
index e7ab7df27ea..3e6b55006fa 100644
--- a/addons/crm/__openerp__.py
+++ b/addons/crm/__openerp__.py
@@ -116,6 +116,7 @@ Dashboard for CRM will include:
'test/crm_lead_onchange.yml',
'test/crm_lead_copy.yml',
'test/crm_lead_unlink.yml',
+ 'test/crm_lead_find_stage.yml',
],
'css': [
'static/src/css/crm.css'
diff --git a/addons/crm/board_crm_view.xml b/addons/crm/board_crm_view.xml
index 19baa5a5709..6bbd450ca38 100644
--- a/addons/crm/board_crm_view.xml
+++ b/addons/crm/board_crm_view.xml
@@ -19,7 +19,11 @@
form
graph,tree,form
- [('state', 'not in', ('done', 'cancel')), ('type', '=', 'opportunity')]
+
+ ['|',
+ '!', '&', ('probability', '=', 100), ('stage_id.on_change', '=', 1),
+ '!', '&', ('probability', '=', 0), ('stage_id.sequence', '!=', 1),
+ ('type', '=', 'opportunity')]
{'search_default_Stage':1}
@@ -43,8 +47,9 @@
form
graph,tree,form
- [('state','!=','cancel'),('opening_date','>',context_today().strftime("%Y-%m-%d"))]
- {'search_default_Stage':1}
+
+ ['!', '&', ('probability', '=', 0), ('stage_id.sequence', '!=', 1)]
+ {'search_default_user': 1, 'search_default_Stage': 1}
diff --git a/addons/crm/crm.py b/addons/crm/crm.py
index d1194508b71..04a17b9dbc0 100644
--- a/addons/crm/crm.py
+++ b/addons/crm/crm.py
@@ -26,15 +26,6 @@ from openerp import tools
from openerp.osv import fields
from openerp.osv import osv
-MAX_LEVEL = 15
-AVAILABLE_STATES = [
- ('draft', 'New'),
- ('cancel', 'Cancelled'),
- ('open', 'In Progress'),
- ('pending', 'Pending'),
- ('done', 'Closed')
-]
-
AVAILABLE_PRIORITIES = [
('1', 'Highest'),
('2', 'High'),
@@ -72,16 +63,13 @@ class crm_case_stage(osv.osv):
'probability': fields.float('Probability (%)', required=True, help="This percentage depicts the default/average probability of the Case for this stage to be a success"),
'on_change': fields.boolean('Change Probability Automatically', help="Setting this stage will change the probability automatically on the opportunity."),
'requirements': fields.text('Requirements'),
- 'section_ids':fields.many2many('crm.case.section', 'section_stage_rel', 'stage_id', 'section_id', string='Sections',
+ 'section_ids': fields.many2many('crm.case.section', 'section_stage_rel', 'stage_id', 'section_id', string='Sections',
help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."),
- 'state': fields.selection(AVAILABLE_STATES, 'Related Status', required=True,
- help="The status of your document will automatically change regarding the selected stage. " \
- "For example, if a stage is related to the status 'Close', when your document reaches this stage, it is automatically closed."),
'case_default': fields.boolean('Default to New Sales Team',
help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."),
'fold': fields.boolean('Fold by Default',
help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
- 'type': fields.selection([ ('lead','Lead'),
+ 'type': fields.selection([('lead', 'Lead'),
('opportunity', 'Opportunity'),
('both', 'Both')],
string='Type', size=16, required=True,
@@ -91,7 +79,7 @@ class crm_case_stage(osv.osv):
_defaults = {
'sequence': lambda *args: 1,
'probability': lambda *args: 0.0,
- 'state': 'open',
+ 'on_change': True,
'fold': False,
'type': 'both',
'case_default': True,
diff --git a/addons/crm/crm_action_rule_demo.xml b/addons/crm/crm_action_rule_demo.xml
index 569b2d676e0..8eac91c06e2 100644
--- a/addons/crm/crm_action_rule_demo.xml
+++ b/addons/crm/crm_action_rule_demo.xml
@@ -5,7 +5,7 @@
Draft Leads
crm.lead
- [('state','=','draft')]
+ [('stage_id.sequence', '=', 1)]
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index c0f003f9792..65d096c6a51 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -22,14 +22,13 @@
import crm
from datetime import datetime
from operator import itemgetter
-from openerp.osv import fields, osv, orm
-import time
+
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',
@@ -61,11 +60,7 @@ CRM_LEAD_FIELDS_TO_MERGE = ['name',
'email_from',
'email_cc',
'partner_name']
-CRM_LEAD_PENDING_STATES = (
- crm.AVAILABLE_STATES[2][0], # Cancelled
- crm.AVAILABLE_STATES[3][0], # Done
- crm.AVAILABLE_STATES[4][0], # Pending
-)
+
class crm_lead(format_address, osv.osv):
""" CRM Lead Case """
@@ -75,13 +70,11 @@ class crm_lead(format_address, osv.osv):
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
- 'state': {
- 'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.state in ['new', 'draft'],
- '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',
- },
'stage_id': {
- 'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: obj.state not in ['new', 'draft', 'cancel', 'done'],
+ '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,
},
}
@@ -99,7 +92,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, [('state', '=', 'draft')], context=context)
+ return self.stage_find(cr, uid, [], section_id, [('sequence', '=', '1')], context=context)
def _resolve_section_id_from_context(self, cr, uid, context=None):
""" Returns ID of section based on the value of 'section_id'
@@ -150,7 +143,7 @@ class crm_lead(format_address, osv.osv):
stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context)
result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context)
# restore order of the search
- result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
+ result.sort(lambda x, y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
fold = {}
for stage in stage_obj.browse(cr, access_rights_uid, stage_ids, context=context):
@@ -158,7 +151,7 @@ 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):
- res = super(crm_lead,self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
+ 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
@@ -220,17 +213,6 @@ class crm_lead(format_address, osv.osv):
res[lead.id][field] = abs(int(duration))
return res
- def _history_search(self, cr, uid, obj, name, args, context=None):
- res = []
- 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)
- 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')]
-
_columns = {
'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null', track_visibility='onchange',
select=True, help="Linked partner (optional). Usually created when converting the lead."),
@@ -243,10 +225,10 @@ class crm_lead(format_address, osv.osv):
'email_from': fields.char('Email', size=128, help="Email address of the contact", select=1),
'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"),
+ 'create_date': fields.datetime('Creation Date', readonly=True),
+ '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),
+ 'write_date': fields.datetime('Update Date', readonly=True),
'categ_ids': fields.many2many('crm.case.categ', 'crm_lead_category_rel', 'lead_id', 'category_id', 'Categories', \
domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"),
'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
@@ -264,17 +246,15 @@ class crm_lead(format_address, osv.osv):
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),
- 'state': fields.related('stage_id', 'state', type="selection", store=True,
- selection=crm.AVAILABLE_STATES, string="Status", readonly=True,
- 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\'.'),
+ 'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
# Only used for type opportunity
- 'probability': fields.float('Success Rate (%)',group_operator="avg"),
+ '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),
@@ -316,6 +296,7 @@ class crm_lead(format_address, osv.osv):
'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 = [
@@ -325,7 +306,7 @@ class crm_lead(format_address, osv.osv):
def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
if not stage_id:
return {'value': {}}
- stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
+ 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}}
@@ -358,22 +339,6 @@ class crm_lead(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 done and cancelled 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 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)
-
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:
@@ -385,16 +350,16 @@ class crm_lead(format_address, osv.osv):
if isinstance(cases, (int, long)):
cases = self.browse(cr, uid, cases, context=context)
# collect all section_ids
- section_ids = []
+ section_ids = set()
types = ['both']
if not cases:
- type = context.get('default_type')
- types += [type]
+ ctx_type = context.get('default_type')
+ types += [ctx_type]
if section_id:
- section_ids.append(section_id)
+ section_ids.add(section_id)
for lead in cases:
if lead.section_id:
- section_ids.append(lead.section_id.id)
+ section_ids.add(lead.section_id.id)
if lead.type not in types:
types.append(lead.type)
# OR all section_ids and OR with case_default
@@ -403,8 +368,7 @@ class crm_lead(format_address, osv.osv):
search_domain += [('|')] * len(section_ids)
for section_id in section_ids:
search_domain.append(('section_ids', '=', section_id))
- else:
- search_domain.append(('case_default', '=', True))
+ search_domain.append(('case_default', '=', True))
# AND with cases types
search_domain.append(('type', 'in', types))
# AND with the domain in parameter
@@ -415,27 +379,29 @@ class crm_lead(format_address, osv.osv):
return stage_ids[0]
return False
- def stage_set(self, cr, uid, ids, stage_id, context=None):
- """ Set the new stage. Now just writes the stage.
- TDE TODO: remove me when removing state
- """
- return self.write(cr, uid, ids, {'stage_id': stage_id}, context=context)
-
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)], context=context)
+ stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0), ('on_change', '=', True), ('sequence', '>', 1)], context=context)
if stage_id:
- self.stage_set(cr, uid, [lead.id], stage_id, context=context)
- return True
+ return self.write(cr, uid, [lead.id], {'stage_id': stage_id}, context=context)
+ 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.'))
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)], context=context)
+ stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0), ('on_change', '=', True), ('sequence', '>', 1)], context=context)
if stage_id:
- self.stage_set(cr, uid, [lead.id], stage_id, context=context)
- return True
+ return self.write(cr, uid, [lead.id], {'stage_id': stage_id}, context=context)
+ 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.'))
def case_escalate(self, cr, uid, ids, context=None):
""" Escalates case to parent level """
@@ -451,20 +417,20 @@ class crm_lead(format_address, osv.osv):
self.write(cr, uid, [case.id], data, context=context)
return True
- def set_priority(self, cr, uid, ids, priority):
+ 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):
"""
@@ -640,7 +606,8 @@ class crm_lead(format_address, osv.osv):
sequenced_opps = []
for opportunity in opportunities:
sequence = -1
- if opportunity.stage_id and opportunity.stage_id.state != 'cancel':
+ # TDE: was "if opportunity.stage_id and opportunity.stage_id.state != 'cancel':"
+ if opportunity.probability == 0 and opportunity.stage_id and opportunity.stage_id.sequence != 1 and opportunity.stage_id.fold:
sequence = opportunity.stage_id.sequence
sequenced_opps.append(((int(sequence != -1 and opportunity.type == 'opportunity'), sequence, -opportunity.id), opportunity))
@@ -701,7 +668,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, [('state', '=', 'draft'),('type', 'in', ('opportunity','both'))], context=context)
+ val['stage_id'] = self.stage_find(cr, uid, [lead], section_id, [('sequence', '=', '1'), ('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):
@@ -710,7 +677,8 @@ class crm_lead(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):
- if lead.state in ('done', 'cancel'):
+ # TDE: was if lead.state in ('done', 'cancel'):
+ if (lead.probability == '100') or (lead.probability == '0' and lead.stage_id.sequence != '1'):
continue
vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context)
self.write(cr, uid, [lead.id], vals, context=context)
@@ -940,6 +908,10 @@ class crm_lead(format_address, osv.osv):
return super(crm_lead, self).create(cr, uid, vals, context=create_context)
def write(self, cr, uid, ids, vals, context=None):
+ # 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'):
onchange_stage_values = self.onchange_stage_id(cr, uid, ids, vals.get('stage_id'), context=context)['value']
vals.update(onchange_stage_values)
diff --git a/addons/crm/crm_lead_data.xml b/addons/crm/crm_lead_data.xml
index 219ff2cdeee..3c299fef704 100644
--- a/addons/crm/crm_lead_data.xml
+++ b/addons/crm/crm_lead_data.xml
@@ -5,71 +5,57 @@
New
-
- draft
-
-
+ 1
+ 0
+ 1
+ 1
both
- Opportunity
-
- open
-
-
- lead
-
-
Dead
-
-
- cancel
-
-
+ 1
+ 0
+ 1
+ 30
lead
Qualification
-
- open
-
-
+ 1
+ 20
+ 1
+ 40
opportunity
Proposition
-
- open
-
-
+ 1
+ 40
+ 50
opportunity
Negotiation
-
- open
-
-
+ 1
+ 60
+ 60
opportunity
Won
-
-
- done
-
-
-
+ 1
+ 100
+ 1
+ 70
opportunity
-
+
Lost
-
-
-
- cancel
-
-
+ 1
+ 1
+ 0
+ 1
+ 80
opportunity
@@ -77,7 +63,7 @@
+ (4, ref('stage_lead7'))]"/>
@@ -161,7 +147,7 @@
Lead Created
crm.lead
- Opportunity created
+ Lead created
Stage Changed
diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml
index 3917fadbd32..dab17888d7a 100644
--- a/addons/crm/crm_lead_demo.xml
+++ b/addons/crm/crm_lead_demo.xml
@@ -6,9 +6,9 @@
lead
Plan to Attend a Training
- Jason Dunagan
+ Jacques Dunagan
Le Club SARL
- jason@leclub.fr
+ jdunagan@leclub.fr
Training Manager
73, rue Léon Dierx
@@ -20,14 +20,25 @@
1
-
+
Hello,
-I am Jason from Le Club SARL.
-I am intertested to attend a training organized in your company.
-Can you send me the details ?
-
+I am Jacques from Le Club SARL.
+I am interested to attend a training organized in your company.
+Could you please send me the details ?
+
+
+
+
+
+ Inquiry
+ crm.lead
+
+ Hello,
+ I am Jacques from Le Club SARL. I am interested to attend a training organized in your company.
+ Can you send me the details ?
]]>
+ email
@@ -44,10 +55,20 @@ Can you send me the details ?
4
-
+
-
+
+
+
+
+
+ Need Details
+ crm.lead
+
+
+ Want to know features and benefits to use the new software.
+ comment
@@ -65,8 +86,10 @@ Can you send me the details ?
2
-
-
+
+
+
+
@@ -82,10 +105,12 @@ Can you send me the details ?
3
-
-
-
-
+
+
+
+
+
+
@@ -105,8 +130,8 @@ Can you send me the details ?
3
-
-
+
+
Hi,
Can you send me a quotation for 20 computers with speakers?
@@ -116,7 +141,6 @@ Purchase Manager
Stonage IT,
Franklinville
Contact: +1 813 494 5005
-
@@ -134,9 +158,8 @@ Contact: +1 813 494 5005
3
-
+
-
@@ -153,9 +176,8 @@ Contact: +1 813 494 5005
5
-
-
-
+
+
@@ -173,9 +195,8 @@ Contact: +1 813 494 5005
4
-
-
-
+
+
@@ -193,12 +214,8 @@ Contact: +1 813 494 5005
2
-
+
-
-
-
-
@@ -215,14 +232,13 @@ Contact: +1 813 494 5005
2
-
+
Hi,
I would like to know more about specification and cost of laptops of your company.
Thanks,
Andrew
-
@@ -239,9 +255,8 @@ Andrew
3
-
+
-
@@ -258,15 +273,23 @@ Andrew
2
-
-
-
+
+
+
+
+
@@ -289,8 +312,7 @@ Andrew
Meeting for pricing information.
-
-
+
@@ -316,7 +338,6 @@ Andrew
-
@@ -337,10 +358,12 @@ Andrew
Call to ask system requirement
-
-
+
+
+
+
opportunity
@@ -362,8 +385,7 @@ Andrew
Convert to quote
-
-
+
@@ -388,10 +410,12 @@ Andrew
Send price list regarding our interventions
-
-
+
+
+
+
opportunity
@@ -413,9 +437,12 @@ Andrew
Call to define real needs about training
-
+
+
+
+
opportunity
@@ -437,8 +464,7 @@ Andrew
Ask for the good receprion of the proposition
-
-
+
@@ -455,8 +481,7 @@ Andrew
2
-
-
+
@@ -470,8 +495,7 @@ Andrew
3
-
-
+
@@ -489,8 +513,7 @@ Andrew
3
-
-
+
@@ -505,8 +528,7 @@ Andrew
5
-
-
+
@@ -525,8 +547,7 @@ Andrew
-
-
+
@@ -549,8 +570,7 @@ Andrew
Conf call with technical service
-
-
+
@@ -574,21 +594,10 @@ Andrew
Send Catalogue by Email
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
bWlncmF0aW9uIHRlc3Q=
@@ -682,26 +691,9 @@ Andrew
-
- Inquiry
- crm.lead
-
- Hello,
- I am Jason from Le Club SARL. I am interested to attend a training organized in your company.
- Can you send me the details ?]]>
- email
-
-
- Need Details
- crm.lead
-
- Want to know features and benefits to use the new software.
- comment
-
-
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index aa5aac76b9d..849cecaac85 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -12,9 +12,10 @@
crm.case.stage
-
-
+
+
+
@@ -94,7 +95,8 @@