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 7204f666db5..0464a4a301d 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 af02e3be1bf..a2fe1aa14f2 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', \ @@ -269,12 +251,9 @@ class crm_lead(format_address, osv.osv): 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\'.'), # 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), @@ -325,7 +304,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,21 +337,22 @@ 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'))) + # TDE NOTE: replaced byu base action rule fully working ? + # 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) + # 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 @@ -385,16 +365,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 +383,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 +394,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 +432,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 +621,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)) diff --git a/addons/crm/crm_lead_data.xml b/addons/crm/crm_lead_data.xml index 92914542d9d..f6adf2b6071 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..0d6d35cacc8 100644 --- a/addons/crm/crm_lead_demo.xml +++ b/addons/crm/crm_lead_demo.xml @@ -1,14 +1,15 @@ - + + 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 +21,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 +56,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 +87,10 @@ Can you send me the details ? 2 - - + + + + @@ -82,10 +106,12 @@ Can you send me the details ? 3 - - - - + + + + + + @@ -105,8 +131,8 @@ Can you send me the details ? 3 - - + + Hi, Can you send me a quotation for 20 computers with speakers? @@ -116,7 +142,6 @@ Purchase Manager Stonage IT, Franklinville Contact: +1 813 494 5005 - @@ -134,9 +159,8 @@ Contact: +1 813 494 5005 3 - + - @@ -153,9 +177,8 @@ Contact: +1 813 494 5005 5 - - - + + @@ -173,9 +196,8 @@ Contact: +1 813 494 5005 4 - - - + + @@ -193,12 +215,8 @@ Contact: +1 813 494 5005 2 - + - - - - @@ -215,14 +233,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 +256,8 @@ Andrew 3 - + - @@ -258,15 +274,23 @@ Andrew 2 - - - + + + + + @@ -289,8 +313,7 @@ Andrew Meeting for pricing information. - - + @@ -316,7 +339,6 @@ Andrew - @@ -337,9 +359,8 @@ Andrew Call to ask system requirement - - - + + @@ -362,9 +383,8 @@ Andrew Convert to quote - - - + + @@ -388,9 +408,8 @@ Andrew Send price list regarding our interventions - - - + + @@ -413,7 +432,7 @@ Andrew Call to define real needs about training - + @@ -437,9 +456,8 @@ Andrew Ask for the good receprion of the proposition - - - + + @@ -455,8 +473,7 @@ Andrew 2 - - + @@ -470,9 +487,8 @@ Andrew 3 - - - + + @@ -489,8 +505,7 @@ Andrew 3 - - + @@ -505,9 +520,8 @@ Andrew 5 - - - + + @@ -525,9 +539,8 @@ Andrew - - - + + @@ -549,8 +562,7 @@ Andrew Conf call with technical service - - + @@ -574,9 +586,8 @@ Andrew Send Catalogue by Email - - - + + @@ -682,26 +693,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 cb4302166a1..e8470cda4bd 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 @@
-
@@ -430,7 +429,8 @@