summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntony Lesuisse <al@openerp.com>2012-12-08 15:41:39 +0100
committerAntony Lesuisse <al@openerp.com>2012-12-08 15:41:39 +0100
commite6efcaeabcff924d6e44f5c0f29edfec5570039d (patch)
treefbf54c5e4d1cc4691b3e0d1a0eff69a17b93e566
parent85b0acf7000d0485e97f044f3fe4a7edcde25f3a (diff)
parentd30c3a32db5cf367eb0d464f3f61f37c41195a15 (diff)
[MERGE] improve_crm_leads_flux by abo
bzr revid: al@openerp.com-20121208144139-o9zr10b7qbij48d5
-rw-r--r--addons/crm/__openerp__.py5
-rw-r--r--addons/crm/crm_action_rule_demo.xml54
-rw-r--r--addons/crm/crm_lead.py286
-rw-r--r--addons/crm/crm_lead_demo.xml27
-rw-r--r--addons/crm/crm_lead_menu.xml80
-rw-r--r--addons/crm/crm_lead_view.xml8
-rw-r--r--addons/crm/crm_phonecall.py38
-rw-r--r--addons/crm/crm_phonecall_view.xml49
-rw-r--r--addons/crm/crm_view.xml2
-rw-r--r--addons/crm/test/process/communication_with_customer.yml18
-rw-r--r--addons/crm/test/process/lead2opportunity2win.yml20
-rw-r--r--addons/crm/test/process/lead2opportunity_assign_salesmen.yml70
-rw-r--r--addons/crm/test/process/merge_opportunity.yml134
-rw-r--r--addons/crm/test/ui/crm_demo.yml8
-rw-r--r--addons/crm/wizard/__init__.py10
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity.py17
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity_view.xml20
-rw-r--r--addons/crm/wizard/crm_lead_to_partner.py122
-rw-r--r--addons/crm/wizard/crm_lead_to_partner_view.xml37
-rw-r--r--addons/crm/wizard/crm_merge_opportunities.py52
-rw-r--r--addons/crm/wizard/crm_partner_binding.py115
-rw-r--r--addons/crm/wizard/crm_partner_to_opportunity.py77
-rw-r--r--addons/crm/wizard/crm_partner_to_opportunity_view.xml27
-rw-r--r--addons/crm/wizard/crm_phonecall_to_opportunity.py65
-rw-r--r--addons/crm/wizard/crm_phonecall_to_opportunity_view.xml42
-rw-r--r--addons/crm/wizard/crm_phonecall_to_partner.py67
-rw-r--r--addons/crm/wizard/crm_phonecall_to_partner_view.xml54
27 files changed, 699 insertions, 805 deletions
diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py
index 63d5d3da84b..76f65cc5bba 100644
--- a/addons/crm/__openerp__.py
+++ b/addons/crm/__openerp__.py
@@ -67,15 +67,11 @@ Dashboard for CRM will include:
'security/crm_security.xml',
'security/ir.model.access.csv',
- 'wizard/crm_lead_to_partner_view.xml',
'wizard/crm_lead_to_opportunity_view.xml',
'wizard/crm_phonecall_to_phonecall_view.xml',
- 'wizard/crm_phonecall_to_partner_view.xml',
- 'wizard/crm_phonecall_to_opportunity_view.xml',
'wizard/crm_opportunity_to_phonecall_view.xml',
- 'wizard/crm_partner_to_opportunity_view.xml',
'wizard/crm_merge_opportunities_view.xml',
@@ -109,6 +105,7 @@ Dashboard for CRM will include:
'test': [
'test/process/communication_with_customer.yml',
'test/process/lead2opportunity2win.yml',
+ 'test/process/lead2opportunity_assign_salesmen.yml',
'test/process/merge_opportunity.yml',
'test/process/cancel_lead.yml',
'test/process/action_rule.yml',
diff --git a/addons/crm/crm_action_rule_demo.xml b/addons/crm/crm_action_rule_demo.xml
index 14a9761e102..6885aba9bff 100644
--- a/addons/crm/crm_action_rule_demo.xml
+++ b/addons/crm/crm_action_rule_demo.xml
@@ -1,7 +1,13 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
- <!-- email server actions -->
+ <!-- demo automated actions add messge on new lead after 5 days -->
+ <record id="filter_draft_lead" model="ir.filters">
+ <field name="name">Draft Leads</field>
+ <field name="model_id">crm.lead</field>
+ <field name="domain">[('state','=','draft')]</field>
+ <field name="user_id" eval="False"/>
+ </record>
<record id="action_email_reminder_lead" model="ir.actions.server">
<field name="name">Reminder to User</field>
<field name="model_id" ref="model_crm_lead"/>
@@ -10,46 +16,12 @@
<field name="state">email</field>
<field name="email">object.user_id.email</field>
<field name="subject">Reminder on Lead: [[object.id ]] [[object.partner_id and 'of ' +object.partner_id.name or '']]</field>
- <field name="message">Hello [[object.user_id.name]],
-can you check following lead, it's not open since 5 days ?
-
-Lead: [[object.id ]]
-Description:
-
- [[object.description]]
-
-Thanks,
- </field>
- </record>
-
- <record id="action_email_reminder_customer_lead" model="ir.actions.server">
- <field name="name">Reminder to Customer</field>
- <field name="model_id" ref="model_crm_lead"/>
- <field name="condition">True</field>
- <field name="type">ir.actions.server</field>
- <field name="state">email</field>
- <field name="email">object.email_from</field>
- <field name="subject">Reminder on Lead: [[object.id ]]</field>
- <field name="message">Hello [[object.partner_id and object.partner_id.name or '']],
-your following lead is not open since 5 days.
-
-Lead: [[object.id ]]
-Description:
-
- [[object.description]]
-
-Thanks,
+ <field name="message">Warning unprocessed incoming lead is more than 5 day old.
+Name: [[object.name ]]
+ID: [[object.id ]]
+Description: [[object.description]]
</field>
</record>
-
- <record id="filter_draft_lead" model="ir.filters">
- <field name="name">Draft Leads</field>
- <field name="model_id">crm.lead</field>
- <field name="domain">[('state','=','draft')]</field>
- <field name="user_id" eval="False"/>
- </record>
-
- <!-- automated actions -->
<record id="rule_set_reminder_lead" model="base.action.rule">
<field name="name">Set Auto Reminder on leads which are not open since 5 days.</field>
<field name="model_id" ref="model_crm_lead"/>
@@ -58,16 +30,16 @@ Thanks,
<field name="trg_date_type">create</field>
<field name="trg_date_range">5</field>
<field name="trg_date_range_type">day</field>
- <field name="server_action_ids" eval="[(6,0,[ref('action_email_reminder_customer_lead'), ref('action_email_reminder_lead')])]"/>
+ <field name="server_action_ids" eval="[(6,0,[ref('action_email_reminder_lead')])]"/>
</record>
+ <!-- demo automated actions add a followers on urgent USA leads -->
<record id="filter_usa_lead" model="ir.filters">
<field name="name">Leads from USA</field>
<field name="model_id">crm.lead</field>
<field name="domain">[('country_id','=','United States')]</field>
<field name="user_id" eval="False"/>
</record>
-
<record id="rule_set_followers_lead" model="base.action.rule">
<field name="name">Set Auto Followers on leads which are urgent and come from USA.</field>
<field name="model_id" ref="model_crm_lead"/>
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index 9ee114d8c4d..d9701d63ba7 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -30,6 +30,36 @@ from tools import html2plaintext
from base.res.res_partner import format_address
+CRM_LEAD_FIELDS_TO_MERGE = ['name',
+ 'partner_id',
+ 'channel_id',
+ 'company_id',
+ 'country_id',
+ 'section_id',
+ 'stage_id',
+ 'state_id',
+ 'type_id',
+ 'user_id',
+ 'title',
+ 'city',
+ 'contact_name',
+ 'description',
+ 'email',
+ 'fax',
+ 'mobile',
+ 'partner_name',
+ 'phone',
+ 'probability',
+ 'planned_revenue',
+ 'street',
+ 'street2',
+ 'zip',
+ 'create_date',
+ 'date_action_last',
+ 'date_action_next',
+ 'email_from',
+ 'email_cc',
+ 'partner_name']
CRM_LEAD_PENDING_STATES = (
crm.AVAILABLE_STATES[2][0], # Cancelled
crm.AVAILABLE_STATES[3][0], # Done
@@ -120,11 +150,7 @@ class crm_lead(base_stage, format_address, osv.osv):
def _compute_day(self, cr, uid, ids, fields, args, context=None):
"""
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of Openday’s IDs
- @return: difference between current date and log date
- @param context: A standard dictionary for contextual values
+ :return dict: difference between current date and log date
"""
cal_obj = self.pool.get('resource.calendar')
res_obj = self.pool.get('resource.resource')
@@ -413,15 +439,46 @@ class crm_lead(base_stage, format_address, osv.osv):
"""
return self.set_priority(cr, uid, ids, '3')
+ def _merge_get_result_type(self, cr, uid, opps, context=None):
+ """
+ Define the type of the result of the merge. If at least one of the
+ element to merge is an opp, the resulting new element will be an opp.
+ Otherwise it will be a lead.
+
+ We'll directly use a list of browse records instead of a list of ids
+ for performances' sake: it will spare a second browse of the
+ leads/opps.
+
+ :param list opps: list of browse records containing the leads/opps to process
+ :return string type: the type of the final element
+ """
+ for opp in opps:
+ if (opp.type == 'opportunity'):
+ return 'opportunity'
+
+ return 'lead'
+
def _merge_data(self, cr, uid, ids, oldest, fields, context=None):
- # prepare opportunity data into dictionary for merging
+ """
+ Prepare lead/opp data into a dictionary for merging. Different types
+ of fields are processed in different ways:
+ - text: all the values are concatenated
+ - m2m and o2m: those fields aren't processed
+ - m2o: the first not null value prevails (the other are dropped)
+ - any other type of field: same as m2o
+
+ :param list ids: list of ids of the leads to process
+ :param list fields: list of leads' fields to process
+ :return dict data: contains the merged values
+ """
opportunities = self.browse(cr, uid, ids, context=context)
+
def _get_first_not_null(attr):
if hasattr(oldest, attr):
return getattr(oldest, attr)
- for opportunity in opportunities:
- if hasattr(opportunity, attr):
- return getattr(opportunity, attr)
+ for opp in opportunities:
+ if hasattr(opp, attr):
+ return getattr(opp, attr)
return False
def _get_first_not_null_id(attr):
@@ -429,8 +486,9 @@ class crm_lead(base_stage, format_address, osv.osv):
return res and res.id or False
def _concat_all(attr):
- return ', '.join(filter(lambda x: x, [getattr(opportunity, attr) or '' for opportunity in opportunities if hasattr(opportunity, attr)]))
+ return ', '.join(filter(lambda x: x, [getattr(opp, attr) or '' for opp in opportunities if hasattr(opp, attr)]))
+ # Process the fields' values
data = {}
for field_name in fields:
field_info = self._all_columns.get(field_name)
@@ -445,24 +503,35 @@ class crm_lead(base_stage, format_address, osv.osv):
data[field_name] = _concat_all(field_name) #not lost
else:
data[field_name] = _get_first_not_null(field_name) #not lost
+
+ # Define the resulting type ('lead' or 'opportunity')
+ data['type'] = self._merge_get_result_type(cr, uid, opportunities, context)
+
return data
def _merge_find_oldest(self, cr, uid, ids, context=None):
+ """
+ Return the oldest lead found among ids.
+
+ :param list ids: list of ids of the leads to inspect
+ :return object: browse record of the oldest of the leads
+ """
if context is None:
context = {}
- #TOCHECK: where pass 'convert' in context ?
+
if context.get('convert'):
- ids = list(set(ids) - set(context.get('lead_ids', False)) )
+ ids = list(set(ids) - set(context.get('lead_ids', [])))
- #search opportunities order by create date
- opportunity_ids = self.search(cr, uid, [('id', 'in', ids)], order='create_date' , context=context)
- oldest_id = opportunity_ids[0]
- return self.browse(cr, uid, oldest_id, context=context)
+ # Search opportunities order by create date
+ opportunity_ids = self.search(cr, uid, [('id', 'in', ids)], order='create_date', context=context)
+ oldest_opp_id = opportunity_ids[0]
+ return self.browse(cr, uid, oldest_opp_id, context=context)
def _mail_body(self, cr, uid, lead, fields, title=False, context=None):
body = []
if title:
body.append("%s\n" % (title))
+
for field_name in fields:
field_info = self._all_columns.get(field_name)
if field_info is None:
@@ -483,20 +552,25 @@ class crm_lead(base_stage, format_address, osv.osv):
value = lead[field_name]
body.append("%s: %s" % (field.string, value or ''))
+
return "\n".join(body + ['---'])
- def _merge_notification(self, cr, uid, opportunity_id, opportunities, context=None):
+ def _merge_notify(self, cr, uid, opportunity_id, opportunities, context=None):
+ """
+ Create a message gathering merged leads/opps information.
+ """
#TOFIX: mail template should be used instead of fix body, subject text
details = []
- merge_message = _('Merged opportunities')
+ result_type = self._merge_get_result_type(cr, uid, opportunities, context)
+ if result_type == 'lead':
+ merge_message = _('Merged leads')
+ else:
+ merge_message = _('Merged opportunities')
subject = [merge_message]
- fields = ['name', 'partner_id', 'stage_id', 'section_id', 'user_id', 'categ_ids', 'channel_id', 'company_id', 'contact_name',
- 'email_from', 'phone', 'fax', 'mobile', 'state_id', 'description', 'probability', 'planned_revenue',
- 'country_id', 'city', 'street', 'street2', 'zip']
for opportunity in opportunities:
subject.append(opportunity.name)
title = "%s : %s" % (merge_message, opportunity.name)
- details.append(self._mail_body(cr, uid, opportunity, fields, title=title, context=context))
+ details.append(self._mail_body(cr, uid, opportunity, CRM_LEAD_FIELDS_TO_MERGE, title=title, context=context))
# Chatter message's subject
subject = subject[0] + ": " + ", ".join(subject[1:])
@@ -540,42 +614,41 @@ class crm_lead(base_stage, format_address, osv.osv):
def merge_opportunity(self, cr, uid, ids, context=None):
"""
- To merge opportunities
- :param ids: list of opportunities ids to merge
+ Different cases of merge:
+ - merge leads together = 1 new lead
+ - merge at least 1 opp with anything else (lead or opp) = 1 new opp
+
+ :param list ids: leads/opportunities ids to merge
+ :return int id: id of the resulting lead/opp
"""
if context is None: context = {}
- #TOCHECK: where pass lead_ids in context?
- lead_ids = context and context.get('lead_ids', []) or []
-
if len(ids) <= 1:
- raise osv.except_osv(_('Warning!'),_('Please select more than one opportunity from the list view.'))
+ raise osv.except_osv(_('Warning!'),_('Please select more than one element (lead or opportunity) from the list view.'))
+
+ lead_ids = context.get('lead_ids', [])
ctx_opportunities = self.browse(cr, uid, lead_ids, context=context)
opportunities = self.browse(cr, uid, ids, context=context)
opportunities_list = list(set(opportunities) - set(ctx_opportunities))
oldest = self._merge_find_oldest(cr, uid, ids, context=context)
- if ctx_opportunities :
+ if ctx_opportunities:
first_opportunity = ctx_opportunities[0]
tail_opportunities = opportunities_list + ctx_opportunities[1:]
else:
first_opportunity = opportunities_list[0]
tail_opportunities = opportunities_list[1:]
- fields = ['partner_id', 'title', 'name', 'categ_ids', 'channel_id', 'city', 'company_id', 'contact_name', 'country_id', 'type_id', 'user_id', 'section_id', 'state_id', 'description', 'email', 'fax', 'mobile',
- 'partner_name', 'phone', 'probability', 'planned_revenue', 'street', 'street2', 'zip', 'create_date', 'date_action_last',
- 'date_action_next', 'email_from', 'email_cc', 'partner_name']
-
- data = self._merge_data(cr, uid, ids, oldest, fields, context=context)
+ merged_data = self._merge_data(cr, uid, ids, oldest, CRM_LEAD_FIELDS_TO_MERGE, context=context)
# Merge messages and attachements into the first opportunity
self._merge_opportunity_history(cr, uid, first_opportunity.id, tail_opportunities, context=context)
self._merge_opportunity_attachments(cr, uid, first_opportunity.id, tail_opportunities, context=context)
# Merge notifications about loss of information
- self._merge_notification(cr, uid, first_opportunity, opportunities, context=context)
+ self._merge_notify(cr, uid, first_opportunity, opportunities, context=context)
# Write merged data into first opportunity
- self.write(cr, uid, [first_opportunity.id], data, context=context)
+ self.write(cr, uid, [first_opportunity.id], merged_data, context=context)
# Delete tail opportunities
self.unlink(cr, uid, [x.id for x in tail_opportunities], context=context)
@@ -599,17 +672,17 @@ class crm_lead(base_stage, format_address, osv.osv):
stage_id = stage_ids and stage_ids[0] or False
return {
- 'planned_revenue': lead.planned_revenue,
- 'probability': lead.probability,
- 'name': lead.name,
- 'partner_id': customer and customer.id or False,
- 'user_id': (lead.user_id and lead.user_id.id),
- 'type': 'opportunity',
- 'stage_id': stage_id or False,
- 'date_action': time.strftime('%Y-%m-%d %H:%M:%S'),
- 'date_open': time.strftime('%Y-%m-%d %H:%M:%S'),
- 'email_from': customer and customer.email or lead.email_from,
- 'phone': customer and customer.phone or lead.phone,
+ 'planned_revenue': lead.planned_revenue,
+ 'probability': lead.probability,
+ 'name': lead.name,
+ 'partner_id': customer and customer.id or False,
+ 'user_id': (lead.user_id and lead.user_id.id),
+ 'type': 'opportunity',
+ 'stage_id': stage_id or False,
+ 'date_action': fields.datetime.now(),
+ 'date_open': fields.datetime.now(),
+ 'email_from': customer and customer.email or lead.email_from,
+ 'phone': customer and customer.phone or lead.phone,
}
def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, section_id=False, context=None):
@@ -668,40 +741,67 @@ 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.id or False})
contact_id = res_partner.address_get(cr, uid, [partner_id])['default']
- res = lead.write({'partner_id' : partner_id, }, context=context)
+ res = lead.write({'partner_id': partner_id}, context=context)
self._lead_set_partner_send_note(cr, uid, [lead.id], context)
return res
- def convert_partner(self, cr, uid, ids, action='create', partner_id=False, context=None):
+ def handle_partner_assignation(self, cr, uid, ids, action='create', partner_id=False, context=None):
"""
- Convert partner based on action.
+ Handle partner assignation during a lead conversion.
if action is 'create', create new partner with contact and assign lead to new partner_id.
- otherwise assign lead to specified partner_id
+ otherwise assign lead to the specified partner_id
+
+ :param list ids: leads/opportunities ids to process
+ :param string action: what has to be done regarding partners (create it, assign an existing one, or nothing)
+ :param int partner_id: partner to assign if any
+ :return dict: dictionary organized as followed: {lead_id: partner_assigned_id}
"""
- if context is None:
- context = {}
+ #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)
partner_ids[lead.id] = partner_id
return partner_ids
- def allocate_salesman(self, cr, uid, ids, user_ids, team_id=False, context=None):
+ def allocate_salesman(self, cr, uid, ids, user_ids=None, team_id=False, context=None):
+ """
+ Assign salesmen and salesteam to a batch of leads. If there are more
+ leads than salesmen, these salesmen will be assigned in round-robin.
+ E.g.: 4 salesmen (S1, S2, S3, S4) for 6 leads (L1, L2, ... L6). They
+ will be assigned as followed: L1 - S1, L2 - S2, L3 - S3, L4 - S4,
+ L5 - S1, L6 - S2.
+
+ :param list ids: leads/opportunities ids to process
+ :param list user_ids: salesmen to assign
+ :param int team_id: salesteam to assign
+ :return bool
+ """
index = 0
+
for lead_id in ids:
value = {}
if team_id:
value['section_id'] = team_id
if user_ids:
value['user_id'] = user_ids[index]
+ # Cycle through user_ids
index = (index + 1) % len(user_ids)
if value:
self.write(cr, uid, [lead_id], value, context=context)
@@ -709,7 +809,7 @@ class crm_lead(base_stage, format_address, osv.osv):
def schedule_phonecall(self, cr, uid, ids, schedule_time, call_summary, desc, phone, contact_name, user_id=False, section_id=False, categ_id=False, action='schedule', context=None):
"""
- action :('schedule','Schedule a call'), ('log','Log a call')
+ :param string action: ('schedule','Schedule a call'), ('log','Log a call')
"""
phonecall = self.pool.get('crm.phonecall')
model_data = self.pool.get('ir.model.data')
@@ -724,17 +824,17 @@ class crm_lead(base_stage, format_address, osv.osv):
if not user_id:
user_id = lead.user_id and lead.user_id.id or False
vals = {
- 'name' : call_summary,
- 'opportunity_id' : lead.id,
- 'user_id' : user_id or False,
- 'categ_id' : categ_id or False,
- 'description' : desc or '',
- 'date' : schedule_time,
- 'section_id' : section_id or False,
- 'partner_id': lead.partner_id and lead.partner_id.id or False,
- 'partner_phone' : phone or lead.phone or (lead.partner_id and lead.partner_id.phone or False),
- 'partner_mobile' : lead.partner_id and lead.partner_id.mobile or False,
- 'priority': lead.priority,
+ 'name': call_summary,
+ 'opportunity_id': lead.id,
+ 'user_id': user_id or False,
+ 'categ_id': categ_id or False,
+ 'description': desc or '',
+ 'date': schedule_time,
+ 'section_id': section_id or False,
+ 'partner_id': lead.partner_id and lead.partner_id.id or False,
+ 'partner_phone': phone or lead.phone or (lead.partner_id and lead.partner_id.phone or False),
+ 'partner_mobile': lead.partner_id and lead.partner_id.mobile or False,
+ 'priority': lead.priority,
}
new_id = phonecall.create(cr, uid, vals, context=context)
phonecall.case_open(cr, uid, [new_id], context=context)
@@ -744,30 +844,50 @@ class crm_lead(base_stage, format_address, osv.osv):
self.schedule_phonecall_send_note(cr, uid, [lead.id], new_id, action, context=context)
return phonecall_dict
-
def redirect_opportunity_view(self, cr, uid, opportunity_id, context=None):
models_data = self.pool.get('ir.model.data')
- # Get Opportunity views
- form_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor')
- tree_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_tree_view_oppor')
+ # Get opportunity views
+ dummy, form_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor')
+ dummy, tree_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_tree_view_oppor')
+ return {
+ 'name': _('Opportunity'),
+ 'view_type': 'form',
+ 'view_mode': 'tree, form',
+ 'res_model': 'crm.lead',
+ 'domain': [('type', '=', 'opportunity')],
+ 'res_id': int(opportunity_id),
+ 'view_id': False,
+ 'views': [(form_view or False, 'form'),
+ (tree_view or False, 'tree'),
+ (False, 'calendar'), (False, 'graph')],
+ 'type': 'ir.actions.act_window',
+ }
+
+ def redirect_lead_view(self, cr, uid, lead_id, context=None):
+ models_data = self.pool.get('ir.model.data')
+
+ # Get lead views
+ dummy, form_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_form_view_leads')
+ dummy, tree_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_tree_view_leads')
return {
- 'name': _('Opportunity'),
- 'view_type': 'form',
- 'view_mode': 'tree, form',
- 'res_model': 'crm.lead',
- 'domain': [('type', '=', 'opportunity')],
- 'res_id': int(opportunity_id),
- 'view_id': False,
- 'views': [(form_view and form_view[1] or False, 'form'),
- (tree_view and tree_view[1] or False, 'tree'),
- (False, 'calendar'), (False, 'graph')],
- 'type': 'ir.actions.act_window',
+ 'name': _('Lead'),
+ 'view_type': 'form',
+ 'view_mode': 'tree, form',
+ 'res_model': 'crm.lead',
+ 'domain': [('type', '=', 'lead')],
+ 'res_id': int(lead_id),
+ 'view_id': False,
+ 'views': [(form_view or False, 'form'),
+ (tree_view or False, 'tree'),
+ (False, 'calendar'), (False, 'graph')],
+ 'type': 'ir.actions.act_window',
}
def action_makeMeeting(self, cr, uid, ids, context=None):
- """ This opens Meeting's calendar view to schedule meeting on current Opportunity
- @return : Dictionary value for created Meeting view
+ """
+ Open meeting's calendar view to schedule meeting on current opportunity.
+ :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)
diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml
index 913ce9fc612..6d392097d84 100644
--- a/addons/crm/crm_lead_demo.xml
+++ b/addons/crm/crm_lead_demo.xml
@@ -247,7 +247,7 @@ Andrew</field>
<field name="contact_name">Bojing Hú</field>
<field name="partner_name">Incom Corporation</field>
<field name="email_from">bhu.a100@gmail.com</field>
- <field name="partner_id" ref=""/>
+ <field name="partner_id" ref="base.res_partner_1"/>
<field name="country_id" ref="base.cn"/>
<field name="city">Shenzhen</field>
<field name="type_id" ref="type_lead3"/>
@@ -548,6 +548,31 @@ Andrew</field>
<field eval="1" name="active"/>
</record>
+ <record id="crm_case_26" model="crm.lead">
+ <field name="type">opportunity</field>
+ <field name="name">Would appreciate more information about your products</field>
+ <field eval="11000" name="planned_revenue"/>
+ <field eval="45.0" name="probability"/>
+ <field name="partner_name">Agrolait</field>
+ <field name="email_from">info@agrolait.com</field>
+ <field name="partner_id" ref="base.res_partner_2"/>
+ <field name="street">69 rue de Chimay</field>
+ <field name="country_id" ref="base.be"/>
+ <field name="city">Wavre</field>
+ <field name="zip">1300</field>
+ <field name="type_id" ref="type_lead3"/>
+ <field name="categ_ids" eval="[(6, 0, [categ_oppor2])]"/>
+ <field name="channel_id" ref="crm_case_channel_website"/>
+ <field name="priority">3</field>
+ <field eval="time.strftime('%Y-%m-23')" name="date_deadline"/>
+ <field eval="time.strftime('%Y-%m-10')" name="date_action"/>
+ <field name="title_action">Send Catalogue by Email</field>
+ <field name="section_id" ref="crm_case_section_4"/>
+ <field name="user_id" ref="base.user_demo"/>
+ <field name="stage_id" ref="crm.stage_lead3"/>
+ <field eval="1" name="active"/>
+ </record>
+
<!-- Unsubscribe Admin from case15, subscribe Demo -->
<record id="crm_case_15" model="crm.lead">
<field name="message_follower_ids" eval="[(3, ref('base.partner_root')), (4, ref('base.partner_demo'))]"/>
diff --git a/addons/crm/crm_lead_menu.xml b/addons/crm/crm_lead_menu.xml
index 208d92a6684..8c38ed41a1c 100644
--- a/addons/crm/crm_lead_menu.xml
+++ b/addons/crm/crm_lead_menu.xml
@@ -12,7 +12,7 @@
<field name="context">{'default_type':'lead', 'stage_type':'lead'}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
- Click to create an unqualified lead.
+ Click to create an unqualified lead.
</p><p>
Use leads if you need a qualification step before creating an
opportunity or a customer. It can be a business card you received,
@@ -39,48 +39,48 @@
<field name="act_window_id" ref="crm_case_category_act_leads_all"/>
</record>
- <record model="ir.actions.act_window" id="crm_case_category_act_oppor11">
- <field name="name">Opportunities</field>
- <field name="res_model">crm.lead</field>
- <field name="view_mode">kanban,tree,graph,form,calendar</field>
- <field name="domain">[('type','=','opportunity')]</field>
- <field name="context">{'stage_type': 'opportunity', 'default_type': 'opportunity'}</field>
- <field name="view_id" eval="False"/>
- <field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
- <field name="help" type="html">
- <p class="oe_view_nocontent_create">
- Click to create a new opportunity.
- </p><p>
- OpenERP helps you keep track of your sales pipeline to follow
- up potential sales and better forecast your future revenues.
- </p><p>
- You will be able to plan meetings and phone calls from
- opportunities, convert them into quotations, attach related
- documents, track all discussions, and much more.
- </p>
- </field>
- </record>
+ <record model="ir.actions.act_window" id="crm_case_category_act_oppor11">
+ <field name="name">Opportunities</field>
+ <field name="res_model">crm.lead</field>
+ <field name="view_mode">kanban,tree,graph,form,calendar</field>
+ <field name="domain">[('type','=','opportunity')]</field>
+ <field name="context">{'stage_type': 'opportunity', 'default_type': 'opportunity', 'default_user_id': uid}</field>
+ <field name="view_id" eval="False"/>
+ <field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
+ <field name="help" type="html">
+ <p class="oe_view_nocontent_create">
+ Click to create a new opportunity.
+ </p><p>
+ OpenERP helps you keep track of your sales pipeline to follow
+ up potential sales and better forecast your future revenues.
+ </p><p>
+ You will be able to plan meetings and phone calls from
+ opportunities, convert them into quotations, attach related
+ documents, track all discussions, and much more.
+ </p>
+ </field>
+ </record>
- <record model="ir.actions.act_window.view" id="action_crm_tag_kanban_view_oppor11">
- <field name="sequence" eval="0"/>
- <field name="view_mode">kanban</field>
- <field name="view_id" ref="crm_case_kanban_view_leads"/>
- <field name="act_window_id" ref="crm_case_category_act_oppor11"/>
- </record>
+ <record model="ir.actions.act_window.view" id="action_crm_tag_kanban_view_oppor11">
+ <field name="sequence" eval="0"/>
+ <field name="view_mode">kanban</field>
+ <field name="view_id" ref="crm_case_kanban_view_leads"/>
+ <field name="act_window_id" ref="crm_case_category_act_oppor11"/>
+ </record>
- <record model="ir.actions.act_window.view" id="action_crm_tag_tree_view_oppor11">
- <field name="sequence" eval="1"/>
- <field name="view_mode">tree</field>
- <field name="view_id" ref="crm_case_tree_view_oppor"/>
- <field name="act_window_id" ref="crm_case_category_act_oppor11"/>
- </record>
+ <record model="ir.actions.act_window.view" id="action_crm_tag_tree_view_oppor11">
+ <field name="sequence" eval="1"/>
+ <field name="view_mode">tree</field>
+ <field name="view_id" ref="crm_case_tree_view_oppor"/>
+ <field name="act_window_id" ref="crm_case_category_act_oppor11"/>
+ </record>
- <record model="ir.actions.act_window.view" id="action_crm_tag_form_view_oppor11">
- <field name="sequence" eval="2"/>
- <field name="view_mode">form</field>
- <field name="view_id" ref="crm_case_form_view_oppor"/>
- <field name="act_window_id" ref="crm_case_category_act_oppor11"/>
- </record>
+ <record model="ir.actions.act_window.view" id="action_crm_tag_form_view_oppor11">
+ <field name="sequence" eval="2"/>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="crm_case_form_view_oppor"/>
+ <field name="act_window_id" ref="crm_case_category_act_oppor11"/>
+ </record>
<menuitem name="Sales" id="base.menu_sales" parent="base.menu_base_partner" sequence="1" />
<menuitem name="Leads" id="menu_crm_leads" parent="base.menu_sales" action="crm_case_category_act_leads_all" sequence="2" />
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index 3710df3138f..c64dca542b0 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -116,8 +116,11 @@
<group>
<group>
<field name="partner_name" string="Company Name"/>
+ <!-- Preload all the partner's information -->
<field name="partner_id" string="Customer"
- on_change="on_change_partner(partner_id)" options='{"create_name_field": "name"}'/>
+ on_change="on_change_partner(partner_id)"
+ options='{"create_name_field": "name"}'
+ context="{'default_name': contact_name, 'default_street': street, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_function': function, 'default_phone': phone, 'default_mobile': mobile, 'default_fax': fax, 'default_email': email_from, 'default_user_id': user_id, 'default_section_id': section_id}"/>
<label for="street" string="Address"/>
<div>
<field name="street" placeholder="Street..."/>
@@ -133,7 +136,7 @@
<group>
<label for="contact_name"/>
<div>
- <field name="contact_name" class="oe_inline"/>,
+ <field name="contact_name" class="oe_inline"/><span attrs="{'invisible': [('title', '=', '')]}">, </span>
<field name="title" placeholder="Title" domain="[('domain', '=', 'contact')]" class="oe_inline" options='{"no_open": True}'/>
</div>
<field name="email_from" widget="email"/>
@@ -206,7 +209,6 @@
</field>
</record>
-
<!-- CRM Lead Tree View -->
<record model="ir.ui.view" id="crm_case_tree_view_leads">
<field name="name">Leads</field>
diff --git a/addons/crm/crm_phonecall.py b/addons/crm/crm_phonecall.py
index 9b63f984b4a..0d17e0f3a68 100644
--- a/addons/crm/crm_phonecall.py
+++ b/addons/crm/crm_phonecall.py
@@ -182,17 +182,23 @@ class crm_phonecall(base_state, osv.osv):
'phone': phonecall.partner_phone,
})
- def convert_partner(self, cr, uid, ids, action='create', partner_id=False, context=None):
+ def handle_partner_assignation(self, cr, uid, ids, action='create', partner_id=False, context=None):
"""
- This function convert partner based on action.
+ Handle partner assignation during a lead conversion.
if action is 'create', create new partner with contact and assign lead to new partner_id.
otherwise assign lead to specified partner_id
+
+ :param list ids: phonecalls ids to process
+ :param string action: what has to be done regarding partners (create it, assign an existing one, or nothing)
+ :param int partner_id: partner to assign if any
+ :return dict: dictionary organized as followed: {lead_id: partner_assigned_id}
"""
- if context is None:
- context = {}
+ #TODO this is a duplication of the handle_partner_assignation method of crm_lead
partner_ids = {}
+ # If a partner_id is given, force this partner for all elements
force_partner_id = partner_id
for call 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._call_create_partner(cr, uid, call, context=context)
self._call_create_partner_address(cr, uid, call, partner_id, context=context)
@@ -219,7 +225,6 @@ class crm_phonecall(base_state, osv.osv):
}
return value
-
def convert_opportunity(self, cr, uid, ids, opportunity_summary=False, partner_id=False, planned_revenue=0.0, probability=0.0, context=None):
partner = self.pool.get('res.partner')
opportunity = self.pool.get('crm.lead')
@@ -256,8 +261,9 @@ class crm_phonecall(base_state, osv.osv):
return opportunity_dict
def action_make_meeting(self, cr, uid, ids, context=None):
- """ This opens Meeting's calendar view to schedule meeting on current Phonecall
- @return : Dictionary value for created Meeting view
+ """
+ Open meeting's calendar view to schedule a meeting on current phonecall.
+ :return dict: dictionary value for created meeting view
"""
phonecall = 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)
@@ -270,14 +276,27 @@ class crm_phonecall(base_state, osv.osv):
'default_name': phonecall.name,
}
return res
-
+
+ def action_button_convert2opportunity(self, cr, uid, ids, context=None):
+ """
+ Convert a phonecall into an opp and then redirect to the opp view.
+
+ :param list ids: list of calls ids to convert (typically contains a single id)
+ :return dict: containing view information
+ """
+ if len(ids) != 1:
+ raise osv.except_osv(_('Warning!'),_('It\'s only possible to convert one phonecall at a time.'))
+
+ opportunity_dict = self.convert_opportunity(cr, uid, ids, context=context)
+ return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, opportunity_dict[ids[0]], context)
+
# ----------------------------------------
# OpenChatter
# ----------------------------------------
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
return 'Phonecall'
-
+
def case_reset_send_note(self, cr, uid, ids, context=None):
message = _('Phonecall has been <b>reset and set as open</b>.')
return self.message_post(cr, uid, ids, body=message, context=context)
@@ -300,5 +319,4 @@ class crm_phonecall(base_state, osv.osv):
def _call_set_partner_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Partner has been <b>created</b>."), context=context)
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml
index 3dc93125ced..e070f8fd8d3 100644
--- a/addons/crm/crm_phonecall_view.xml
+++ b/addons/crm/crm_phonecall_view.xml
@@ -3,7 +3,7 @@
<data>
-<!-- Phonecall Categories Form View -->
+ <!-- Phonecall Categories Form View -->
<record id="crm_phonecall_categ_action" model="ir.actions.act_window">
<field name="name">Phonecall Categories</field>
<field name="res_model">crm.case.categ</field>
@@ -24,8 +24,7 @@
<menuitem action="crm_phonecall_categ_action" name="Categories"
id="menu_crm_case_phonecall-act" parent="menu_crm_config_phonecall" groups="base.group_no_one"/>
-<!-- PhoneCalls Tree View -->
-
+ <!-- Phonecalls Tree View -->
<record model="ir.ui.view" id="crm_case_phone_tree_view">
<field name="name">CRM - Phone Calls Tree</field>
<field name="model">crm.phonecall</field>
@@ -35,13 +34,14 @@
<field name="name"/>
<field name="partner_id"/>
<button string="Meeting"
- states="open,pending" icon="gtk-redo"
- name="action_make_meeting" type="object"/>
+ states="open,pending"
+ name="action_make_meeting"
+ type="object"/>
<button string="Convert to Opportunity"
- name="%(phonecall2opportunity_act)d"
+ type="object"
+ name="action_button_convert2opportunity"
states="open,pending"
- icon="gtk-index"
- type="action" attrs="{'invisible':[('opportunity_id','!=',False)]}"/>
+ attrs="{'invisible':[('opportunity_id','!=',False)]}"/>
<field name="partner_phone"/>
<field name="user_id"/>
<field name="categ_id" invisible="1"/>
@@ -60,27 +60,26 @@
</field>
</record>
-<!-- Phonecalls Form View -->
-
+ <!-- Phonecalls Form View -->
<record model="ir.ui.view" id="crm_case_phone_form_view">
<field name="name">CRM - Phone Call Form</field>
<field name="model">crm.phonecall</field>
<field name="arch" type="xml">
<form string="Phone Call" version="7.0">
- <header>
- <button name="case_close" string="Call Done" type="object" class="oe_highlight"
- states="open,pending"/>
- <button name="case_reset" string="Reset to Todo" type="object"
- states="cancel"/>
- <button name="case_cancel" string="Cancel" type="object"
- states="draft,open,pending"/>
- <field name="state" widget="statusbar" nolabel="1" statusbar_visible="open,done"/>
- </header>
+ <header>
+ <button name="case_close" string="Call Done" type="object" class="oe_highlight"
+ states="open,pending"/>
+ <button name="case_reset" string="Reset to Todo" type="object"
+ states="cancel"/>
+ <button name="case_cancel" string="Cancel" type="object"
+ states="draft,open,pending"/>
+ <field name="state" widget="statusbar" nolabel="1" statusbar_visible="open,done"/>
+ </header>
<sheet string="Phone Call">
- <div class="oe_right">
+ <div class="oe_right">
<button string="Convert to Opportunity"
- name="%(phonecall2opportunity_act)d"
- type="action"
+ type="object"
+ name="action_button_convert2opportunity"
attrs="{'invisible':[ '|', ('opportunity_id','!=',False), ('state', 'not in', ('open', 'done'))]}"/>
<button string="Schedule Other Call"
name="%(phonecall_to_phonecall_act)d"
@@ -97,7 +96,7 @@
</div>
<h2><field name="partner_phone"/></h2>
</div>
-
+
<group col="4">
<field name="date"/>
<field name="user_id"/>
@@ -122,7 +121,6 @@
</record>
<!--Logged Phonecalls Tree View -->
-
<record model="ir.ui.view" id="crm_case_inbound_phone_tree_view">
<field name="name">CRM - Logged Phone Calls Tree</field>
<field name="model">crm.phonecall</field>
@@ -149,7 +147,7 @@
icon="gtk-redo"
name="action_make_meeting" type="object"/>
<button string="Convert to Opportunity"
- name="%(phonecall2opportunity_act)d"
+ name="action_button_convert2opportunity"
states="open,pending"
icon="gtk-index"
type="action" attrs="{'invisible':[('opportunity_id','!=',False)]}"/>
@@ -158,7 +156,6 @@
</record>
<!-- Phonecalls Calendar View -->
-
<record model="ir.ui.view" id="crm_case_phone_calendar_view">
<field name="name">CRM - Phone Calls Calendar</field>
<field name="model">crm.phonecall</field>
diff --git a/addons/crm/crm_view.xml b/addons/crm/crm_view.xml
index 99d3752529e..529388c827d 100644
--- a/addons/crm/crm_view.xml
+++ b/addons/crm/crm_view.xml
@@ -293,7 +293,7 @@
<field name="view_id" ref="crm_case_resource_type_tree"/>
</record>
- <menuitem action="crm_case_resource_type_act"
+ <menuitem action="crm_case_resource_type_act"
id="menu_crm_case_resource_type_act" sequence="4"
groups="base.group_no_one"
parent="base.menu_crm_config_lead"/>
diff --git a/addons/crm/test/process/communication_with_customer.yml b/addons/crm/test/process/communication_with_customer.yml
index 2137616ae3e..148d08dfc49 100644
--- a/addons/crm/test/process/communication_with_customer.yml
+++ b/addons/crm/test/process/communication_with_customer.yml
@@ -33,20 +33,4 @@
-
!python {model: crm.lead}: |
lead_ids = self.search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')])
- self.convert_partner(cr, uid, lead_ids, context=context)
--
- I convert one phonecall request to a customer and put him into regular customer list.
--
- !python {model: crm.phonecall2partner}: |
- context.update({'active_model': 'crm.phonecall', 'active_ids': [ref("crm.crm_phonecall_4")], 'active_id': ref("crm.crm_phonecall_4")})
- new_id = self.create(cr, uid, {}, context=context)
- self.make_partner(cr, uid, [new_id], context=context)
--
- I check converted phonecall to partner.
--
- !python {model: res.partner}: |
- partner_id = self.search(cr, uid, [('phonecall_ids', '=', ref('crm.crm_phonecall_4'))])
- assert partner_id, "Customer is not found in regular customer list."
- data = self.browse(cr, uid, partner_id, context=context)[0]
- assert data.user_id.id == ref("base.user_root"), "User not assigned properly"
- assert data.name == "Wanted information about pricing of laptops", "Bad partner name"
+ self.handle_partner_assignation(cr, uid, lead_ids, context=context)
diff --git a/addons/crm/test/process/lead2opportunity2win.yml b/addons/crm/test/process/lead2opportunity2win.yml
index dc5aadbcf16..94e7149ef77 100644
--- a/addons/crm/test/process/lead2opportunity2win.yml
+++ b/addons/crm/test/process/lead2opportunity2win.yml
@@ -1,26 +1,16 @@
-
- In order to test convert customer lead into opportunity,
+ In order to test the conversion of a lead into a opportunity,
-
- I open customer lead.
+ I open a lead.
-
!python {model: crm.lead}: |
self.case_open(cr, uid, [ref("crm_case_4")])
-
- I check lead state is "Open".
+ I check if the lead state is "Open".
-
- !assert {model: crm.lead, id: crm.crm_case_4, string: Lead in open state}:
+ !assert {model: crm.lead, id: crm.crm_case_4, string: Lead state is Open}:
- state == "open"
-
- I fill in a lead2partner wizard.
--
- !record {model: crm.lead2partner, id: crm_lead2partner_id1, context: '{"active_model": "crm.lead", "active_ids": [ref("crm_case_4")]}'}:
--
- I create a partner from the lead2partner wizard.
--
- !python {model: crm.lead2partner}: |
- context.update({'active_model': 'crm.lead', 'active_ids': [ref('crm_case_4')], 'active_id': ref('crm_case_4')})
- self.make_partner(cr, uid ,[ref("crm_lead2partner_id1")], context=context)
--
I convert lead into opportunity for exiting customer.
-
!python {model: crm.lead}: |
@@ -76,7 +66,7 @@
-
!python {model: crm.lead2opportunity.partner.mass}: |
context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_11"), ref("crm_case_2")], 'active_id': ref("crm_case_11")})
- id = self.create(cr, uid, {'user_ids': [ref('base.user_root')], 'section_id': ref('crm.section_sales_department')}, context=context)
+ id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('base.user_root')])], 'section_id': ref('crm.section_sales_department')}, context=context)
self.mass_convert(cr, uid, [id], context=context)
-
Now I check first lead converted on opportunity.
diff --git a/addons/crm/test/process/lead2opportunity_assign_salesmen.yml b/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
new file mode 100644
index 00000000000..b538a3a0185
--- /dev/null
+++ b/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
@@ -0,0 +1,70 @@
+-
+ During a lead to opp conversion, salesmen should be assigned to leads following the round-robin method. Start by creating 6 leads (1 to 6) and 4 salesmen (A to D).
+-
+ !record {model: crm.lead, id: test_crm_lead_01}:
+ type: 'lead'
+ name: 'Test lead 1'
+ stage_id: stage_lead1
+-
+ !record {model: crm.lead, id: test_crm_lead_02}:
+ type: 'lead'
+ name: 'Test lead 2'
+ stage_id: stage_lead1
+-
+ !record {model: crm.lead, id: test_crm_lead_03}:
+ type: 'lead'
+ name: 'Test lead 3'
+ stage_id: stage_lead1
+-
+ !record {model: crm.lead, id: test_crm_lead_04}:
+ type: 'lead'
+ name: 'Test lead 4'
+ stage_id: stage_lead1
+-
+ !record {model: crm.lead, id: test_crm_lead_05}:
+ type: 'lead'
+ name: 'Test lead 5'
+ stage_id: stage_lead1
+-
+ !record {model: crm.lead, id: test_crm_lead_06}:
+ type: 'lead'
+ name: 'Test lead 6'
+ stage_id: stage_lead1
+-
+ !record {model: res.users, id: test_res_user_01}:
+ name: 'Test user A'
+ login: 'tua'
+ new_password: 'tua'
+-
+ !record {model: res.users, id: test_res_user_02}:
+ name: 'Test user B'
+ login: 'tub'
+ new_password: 'tub'
+-
+ !record {model: res.users, id: test_res_user_03}:
+ name: 'Test user C'
+ login: 'tuc'
+ new_password: 'tuc'
+-
+ !record {model: res.users, id: test_res_user_04}:
+ name: 'Test user D'
+ login: 'tud'
+ new_password: 'tud'
+-
+ I create a mass convert wizard and convert all the leads.
+-
+ !python {model: crm.lead2opportunity.partner.mass}: |
+ context.update({'active_model': 'crm.lead', 'active_ids': [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], 'active_id': ref("test_crm_lead_01")})
+ id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('test_res_user_01'), ref('test_res_user_02'), ref('test_res_user_03'), ref('test_res_user_04')])], 'section_id': ref('crm.section_sales_department')}, context=context)
+ self.mass_convert(cr, uid, [id], context=context)
+-
+ The leads should now be opps with a salesman and a salesteam. Also, salesmen should have been assigned following a round-robin method.
+-
+ !python {model: crm.lead}: |
+ opps = self.browse(cr, uid, [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], context)
+ salesmen_ids = [ref('test_res_user_01'), ref('test_res_user_02'), ref('test_res_user_03'), ref('test_res_user_04')]
+ i = 0
+ for opp in opps:
+ assert opp.type == 'opportunity', 'Type mismatch: this should be an opp, not a lead'
+ assert opp.user_id.id == salesmen_ids[i], 'Salesman mismatch: expected salesman %r, got %r' % (salesmen_ids[i], opp.user_id.id)
+ i = i+1 if (i < len(salesmen_ids) - 1) else 0 \ No newline at end of file
diff --git a/addons/crm/test/process/merge_opportunity.yml b/addons/crm/test/process/merge_opportunity.yml
index 155cd68274f..2f0b8e82956 100644
--- a/addons/crm/test/process/merge_opportunity.yml
+++ b/addons/crm/test/process/merge_opportunity.yml
@@ -1,34 +1,126 @@
+
-
- I make an opportunity from customer.
+ During a mixed merge (involving leads and opps), data should be handled a certain way following their type (m2o, m2m, text, ...) Start by creating two leads and an opp.
-
- !python {model: crm.partner2opportunity}: |
- context.update({'active_model': 'res.partner', 'active_ids': [ref("base.res_partner_9")]})
- res_id = self.create(cr, uid, {'name': "Enquête pour l'achat de services"}, context=context)
- self.make_opportunity(cr, uid, [res_id], context=context)
+ !record {model: crm.lead, id: test_crm_lead_01}:
+ type: 'lead'
+ name: 'Test lead 1'
+ partner_id: base.res_partner_1
+ stage_id: stage_lead1
+ description: This is the description of the test lead 1.
-
- I make another opportunity from a phone call with the same customer.
+ !record {model: crm.lead, id: test_crm_lead_02}:
+ type: 'lead'
+ name: 'Test lead 2'
+ partner_id: base.res_partner_3
+ stage_id: stage_lead1
+ description: This is the description of the test lead 2.
-
- !python {model: crm.phonecall2opportunity}: |
- context.update({'active_model': 'crm.phonecall', 'active_ids': [ref("crm.crm_phonecall_6")]})
- res_id = self.create(cr, uid, {'name': "À propos du prix de votre autre service", 'partner_id': ref("base.res_partner_9")}, context=context)
- self.make_opportunity(cr, uid, [res_id], context=context)
+ !record {model: crm.lead, id: test_crm_opp_01}:
+ type: 'opportunity'
+ name: 'Test opportunity 1'
+ partner_id: base.res_partner_5
+ stage_id: stage_lead1
+ description: This is the description of the test opp 1.
-
- Now I merge these opportunities.
+ !python {model: crm.lead}: |
+ lead_ids = [ref('test_crm_lead_01'), ref('test_crm_lead_02'), ref('test_crm_opp_01')]
+ context.update({'active_model': 'crm.lead', 'active_ids': lead_ids, 'active_id': lead_ids[0]})
+-
+ I create a merge wizard and merge the leads and opp together in the first item of the list.
+-
+ !record {model: crm.merge.opportunity, id: merge_opp_wizard_01}:
+-
+ !python {model: crm.merge.opportunity}: |
+ self.action_merge(cr, uid, [ref("merge_opp_wizard_01")], context=context)
+-
+ I check for the resulting merged opp (based on name and partner).
+-
+ !python {model: crm.lead}: |
+ merge_id = self.search(cr, uid, [('name', '=', 'Test lead 1'), ('partner_id','=', ref("base.res_partner_1"))])
+ assert merge_id, 'Fail to create merge opportunity wizard'
+ merge_result = self.browse(cr, uid, merge_id)[0]
+ assert merge_result.partner_id.id == ref("base.res_partner_1"), 'Partner mismatch: when merging leads/opps with different m2o values, the first not null value prevails (the other are dropped)'
+ assert merge_result.description == 'This is the description of the test lead 1., This is the description of the test lead 2., This is the description of the test opp 1.', 'Description mismatch: when merging leads/opps with different text values, these values should get concatenated and separeted with ", "'
+ assert merge_result.type == 'opportunity', 'Type mismatch: when at least one opp in involved in the merge, the result should be a new opp (instead of %s)' % merge_result.type
+-
+ The other (tailing) leads/opps shouldn't exist anymore.
+-
+ !python {model: crm.lead}: |
+ tailing_lead = self.search(cr, uid, [('id', '=', ref('test_crm_lead_02'))])
+ assert not tailing_lead, 'This tailing lead (id %s) should not exist anymore' % ref('test_crm_lead_02')
+
+ tailing_opp = self.search(cr, uid, [('id', '=', ref('test_crm_opp_01'))])
+ assert not tailing_opp, 'This tailing opp (id %s) should not exist anymore' % ref('test_crm_opp_01')
+-
+ I want to test leads merge. Start by creating two leads (with the same partner).
+-
+ !record {model: crm.lead, id: test_crm_lead_03}:
+ type: 'lead'
+ name: 'Test lead 3'
+ partner_id: base.res_partner_1
+ stage_id: stage_lead1
+-
+ !record {model: crm.lead, id: test_crm_lead_04}:
+ type: 'lead'
+ name: 'Test lead 4'
+ partner_id: base.res_partner_1
+ stage_id: stage_lead1
-
!python {model: crm.lead}: |
- opportunity_ids = self.search(cr, uid, [('partner_id','=', ref("base.res_partner_9"))])
- context.update({'active_model': 'crm.lead', 'active_ids': opportunity_ids, 'active_id': opportunity_ids[0]})
+ lead_ids = [ref('test_crm_lead_03'), ref('test_crm_lead_04')]
+ context.update({'active_model': 'crm.lead', 'active_ids': lead_ids, 'active_id': lead_ids[0]})
+-
+ I create a merge wizard and merge the leads together.
+-
+ !record {model: crm.merge.opportunity, id: merge_opp_wizard_02}:
+-
+ !python {model: crm.merge.opportunity}: |
+ self.action_merge(cr, uid, [ref("merge_opp_wizard_02")], context=context)
+-
+ I check for the resulting merged lead (based on name and partner).
+-
+ !python {model: crm.lead}: |
+ merge_id = self.search(cr, uid, [('name', '=', 'Test lead 3'), ('partner_id','=', ref("base.res_partner_1"))])
+ assert merge_id, 'Fail to create merge opportunity wizard'
+ merge_result = self.browse(cr, uid, merge_id)[0]
+ assert merge_result.partner_id.id == ref("base.res_partner_1"), 'Partner mismatch'
+ assert merge_result.type == 'lead', 'Type mismatch: when leads get merged together, the result should be a new lead (instead of %s)' % merge_result.type
+ tailing_lead = self.search(cr, uid, [('id', '=', ref('test_crm_lead_04'))])
+ assert not tailing_lead, 'This tailing lead (id %s) should not exist anymore' % ref('test_crm_lead_04')
+-
+ I want to test opps merge. Start by creating two opportunities (with the same partner).
+-
+ !record {model: crm.lead, id: test_crm_opp_02}:
+ type: 'opportunity'
+ name: 'Test opportunity 2'
+ partner_id: base.res_partner_5
+ stage_id: stage_lead1
+-
+ !record {model: crm.lead, id: test_crm_opp_03}:
+ type: 'opportunity'
+ name: 'Test opportunity 3'
+ partner_id: base.res_partner_5
+ stage_id: stage_lead1
+-
+ !python {model: crm.lead}: |
+ opp_ids = [ref('test_crm_opp_02'), ref('test_crm_opp_03')]
+ context.update({'active_model': 'crm.lead', 'active_ids': opp_ids, 'active_id': opp_ids[0]})
+-
+ I create a merge wizard and merge the opps together.
-
- !record {model: crm.merge.opportunity, id: opportunity_merge_id }:
+ !record {model: crm.merge.opportunity, id: merge_opp_wizard_03}:
-
!python {model: crm.merge.opportunity}: |
- self.action_merge(cr, uid, [ref("opportunity_merge_id")], context=context)
+ self.action_merge(cr, uid, [ref("merge_opp_wizard_03")], context=context)
-
- I check for merged opportunities for customer.
+ I check for the resulting merged opp (based on name and partner).
-
!python {model: crm.lead}: |
- merge_id = self.search(cr, uid, [('partner_id','=', ref("base.res_partner_9"))])
- assert merge_id, 'Fail to create Merge opportunity'
- merge_data = self.browse(cr, uid, merge_id)[0]
- assert merge_data.type == 'opportunity', 'Merged opportunity type not change!'
- assert merge_data.partner_id.id == ref("base.res_partner_9"), 'Partner missmatch!'
+ merge_id = self.search(cr, uid, [('name', '=', 'Test opportunity 2'), ('partner_id','=', ref("base.res_partner_5"))])
+ assert merge_id, 'Fail to create merge opportunity wizard'
+ merge_result = self.browse(cr, uid, merge_id)[0]
+ assert merge_result.partner_id.id == ref("base.res_partner_5"), 'Partner mismatch'
+ assert merge_result.type == 'opportunity', 'Type mismatch: when opps get merged together, the result should be a new opp (instead of %s)' % merge_result.type
+ tailing_opp = self.search(cr, uid, [('id', '=', ref('test_crm_opp_03'))])
+ assert not tailing_opp, 'This tailing opp (id %s) should not exist anymore' % ref('test_crm_opp_03') \ No newline at end of file
diff --git a/addons/crm/test/ui/crm_demo.yml b/addons/crm/test/ui/crm_demo.yml
index 842b3a4e887..9154ffdf513 100644
--- a/addons/crm/test/ui/crm_demo.yml
+++ b/addons/crm/test/ui/crm_demo.yml
@@ -1,5 +1,5 @@
-
- I create lead record to call of partner onchange, stage onchange and Mailing opt-in onchange method.
+ I create a lead record to call a partner onchange, stage onchange and mailing opt-in onchange method.
-
!record {model: crm.lead, id: crm_case_25}:
name: 'Need more info about your pc2'
@@ -8,7 +8,7 @@
stage_id: crm.stage_lead1
state: draft
-
- I create lead record to call Mailing opt-out onchange method.
+ I create a lead record to call a mailing opt-out onchange method.
-
!record {model: crm.lead, id: crm_case_18}:
name: 'Need 20 Days of Consultancy'
@@ -16,13 +16,13 @@
state: draft
opt_out: True
-
- I create phonecall record to call partner onchange method.
+ I create a phonecall record to call a partner onchange method.
-
!record {model: crm.phonecall, id: crm_phonecall_5}:
name: 'Bad time'
partner_id: base.res_partner_5
-
- I setting next stage "New" for the lead.
+ I set the next stage to "New" for the lead.
-
!python {model: crm.lead}: |
self.stage_next(cr, uid, [ref("crm_case_4")], context={'stage_type': 'lead'})
diff --git a/addons/crm/wizard/__init__.py b/addons/crm/wizard/__init__.py
index 15ca8df670d..7bc2c97e5dd 100644
--- a/addons/crm/wizard/__init__.py
+++ b/addons/crm/wizard/__init__.py
@@ -19,16 +19,10 @@
#
##############################################################################
-import crm_lead_to_partner
-import crm_lead_to_opportunity
+import crm_partner_binding
import crm_phonecall_to_phonecall
import crm_opportunity_to_phonecall
-import crm_phonecall_to_partner
-import crm_partner_to_opportunity
-import crm_phonecall_to_opportunity
-
+import crm_lead_to_opportunity
import crm_merge_opportunities
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py
index ea96e1d16f1..431070ff8f8 100644
--- a/addons/crm/wizard/crm_lead_to_opportunity.py
+++ b/addons/crm/wizard/crm_lead_to_opportunity.py
@@ -27,16 +27,13 @@ import re
class crm_lead2opportunity_partner(osv.osv_memory):
_name = 'crm.lead2opportunity.partner'
_description = 'Lead To Opportunity Partner'
- _inherit = 'crm.lead2partner'
+ _inherit = 'crm.partner.binding'
_columns = {
- 'action': fields.selection([('exist', 'Link to an existing customer'), \
- ('create', 'Create a new customer'), \
- ('nothing', 'Do not link to a customer')], \
- 'Related Customer', required=True),
- 'name': fields.selection([('convert', 'Convert to opportunity'), \
- ('merge', 'Merge with existing opportunities')], \
- 'Conversion Action', required=True),
+ 'name': fields.selection([
+ ('convert', 'Convert to opportunity'),
+ ('merge', 'Merge with existing opportunities')
+ ], 'Conversion Action', required=True),
'opportunity_ids': fields.many2many('crm.lead', string='Opportunities', domain=[('type', '=', 'opportunity')]),
}
@@ -157,8 +154,8 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
_inherit = 'crm.lead2opportunity.partner'
_columns = {
- 'user_ids': fields.many2many('res.users', string='Salesmen'),
- 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
+ 'user_ids': fields.many2many('res.users', string='Salesmen'),
+ 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
}
def default_get(self, cr, uid, fields, context=None):
diff --git a/addons/crm/wizard/crm_lead_to_opportunity_view.xml b/addons/crm/wizard/crm_lead_to_opportunity_view.xml
index 3390ad8f547..1a7bd687a94 100644
--- a/addons/crm/wizard/crm_lead_to_opportunity_view.xml
+++ b/addons/crm/wizard/crm_lead_to_opportunity_view.xml
@@ -7,7 +7,7 @@
<field name="model">crm.lead2opportunity.partner</field>
<field name="arch" type="xml">
<form string="Convert to Opportunity" version="7.0">
- <group>
+ <group name="name">
<field name="name" class="oe_inline"/>
<field name="opportunity_ids" attrs="{'invisible': [('name', '!=', 'merge')]}">
<tree>
@@ -18,7 +18,7 @@
</tree>
</field>
</group>
- <group attrs="{'invisible': [('name', '!=', 'convert')]}">
+ <group name="action" attrs="{'invisible': [('name', '!=', 'convert')]}">
<field name="action" class="oe_inline"/>
<field name="partner_id"
attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"
@@ -51,21 +51,17 @@
<group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
<field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">
<tree>
- <field name="name" />
- <field name="partner_id" />
- <field name="user_id" />
- <field name="section_id" />
+ <field name="name"/>
+ <field name="partner_id"/>
+ <field name="user_id"/>
+ <field name="section_id"/>
</tree>
</field>
</group>
<group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}">
- <field name="section_id" />
- <field name="user_ids" colspan="4">
- <tree>
- <field name="name" />
- </tree>
- </field>
+ <field name="section_id"/>
+ <field name="user_ids" widget="many2many_tags"/>
</group>
<footer>
diff --git a/addons/crm/wizard/crm_lead_to_partner.py b/addons/crm/wizard/crm_lead_to_partner.py
deleted file mode 100644
index c48990f5866..00000000000
--- a/addons/crm/wizard/crm_lead_to_partner.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-from osv import osv, fields
-from tools.translate import _
-
-class crm_lead2partner(osv.osv_memory):
- """ Converts lead to partner """
- _name = 'crm.lead2partner'
- _description = 'Lead to Partner'
-
- _columns = {
- 'action': fields.selection([('exist', 'Link to an existing customer'), \
- ('create', 'Create a new customer')], \
- 'Action', required=True),
- 'partner_id': fields.many2one('res.partner', 'Customer'),
- }
- def view_init(self, cr, uid, fields, context=None):
- """
- Check for precondition before wizard executes.
- """
- if context is None:
- context = {}
- model = context.get('active_model')
- model = self.pool.get(model)
- rec_ids = context and context.get('active_ids', [])
- for this in model.browse(cr, uid, rec_ids, context=context):
- if this.partner_id:
- raise osv.except_osv(_('Warning!'),
- _('A partner is already defined.'))
-
- def _select_partner(self, cr, uid, context=None):
- if context is None:
- context = {}
- if not context.get('active_model') == 'crm.lead' or not context.get('active_id'):
- return False
- partner = self.pool.get('res.partner')
- lead = self.pool.get('crm.lead')
- this = lead.browse(cr, uid, context.get('active_id'), context=context)
- if this.partner_id:
- return this.partner_id.id
- partner_id = False
- if this.email_from:
- partner_ids = partner.search(cr, uid, [('email', '=', this.email_from)], context=context)
- if partner_ids:
- partner_id = partner_ids[0]
- if not this.partner_id and this.partner_name:
- partner_ids = partner.search(cr, uid, [('name', '=', this.partner_name)], context=context)
- if partner_ids:
- partner_id = partner_ids[0]
- return partner_id
-
- def default_get(self, cr, uid, fields, context=None):
- res = super(crm_lead2partner, self).default_get(cr, uid, fields, context=context)
- partner_id = self._select_partner(cr, uid, context=context)
-
- if 'partner_id' in fields:
- res.update({'partner_id': partner_id})
- if 'action' in fields:
- res.update({'action': partner_id and 'exist' or 'create'})
-
- return res
-
- def open_create_partner(self, cr, uid, ids, context=None):
- """
- Open form of create partner.
- """
- view_obj = self.pool.get('ir.ui.view')
- view_id = view_obj.search(cr, uid, [('model', '=', self._name), \
- ('name', '=', self._name+'.view')])
- return {
- 'view_mode': 'form',
- 'view_type': 'form',
- 'view_id': view_id or False,
- 'res_model': self._name,
- 'context': context,
- 'type': 'ir.actions.act_window',
- 'target': 'new',
- }
-
- def _create_partner(self, cr, uid, ids, context=None):
- """
- Create partner based on action.
- """
- if context is None:
- context = {}
- lead = self.pool.get('crm.lead')
- lead_ids = context.get('active_ids', [])
- data = self.browse(cr, uid, ids, context=context)[0]
- partner_id = data.partner_id and data.partner_id.id or False
- return lead.convert_partner(cr, uid, lead_ids, data.action, partner_id, context=context)
-
- def make_partner(self, cr, uid, ids, context=None):
- """
- Make a partner based on action.
- Only called from form view, so only meant to convert one lead at a time.
- """
- lead_id = context and context.get('active_id') or False
- partner_ids_map = self._create_partner(cr, uid, ids, context=context)
- return self.pool.get('res.partner').redirect_partner_form(cr, uid, partner_ids_map.get(lead_id, False), context=context)
-
-crm_lead2partner()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/wizard/crm_lead_to_partner_view.xml b/addons/crm/wizard/crm_lead_to_partner_view.xml
deleted file mode 100644
index 21a14dd3726..00000000000
--- a/addons/crm/wizard/crm_lead_to_partner_view.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<openerp>
- <data>
-
- <!-- Lead to Partner form view -->
-
- <record id="view_crm_lead2partner" model="ir.ui.view">
- <field name="name">crm.lead2partner.view</field>
- <field name="model">crm.lead2partner</field>
- <field name="arch" type="xml">
- <form string="Create a Partner" version="7.0">
- <group col="4">
- <separator string="Create a Partner"/>
- <field name="action"/>
- <field name="partner_id" attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"/>
- </group>
- <footer>
- <button name="make_partner" string="Continue" type="object" class="oe_highlight"/>
- or
- <button string="Cancel" class="oe_link" special="cancel" />
- </footer>
- </form>
- </field>
- </record>
-
-
- <!-- Lead to Partner wizard -->
- <record id="action_crm_lead2partner" model="ir.actions.act_window">
- <field name="name">Create a Partner</field>
- <field name="type">ir.actions.act_window</field>
- <field name="res_model">crm.lead2partner</field>
- <field name="view_type">form</field>
- <field name="view_id" ref="view_crm_lead2partner"/>
- <field name="target">new</field>
- </record>
-
- </data>
-</openerp>
diff --git a/addons/crm/wizard/crm_merge_opportunities.py b/addons/crm/wizard/crm_merge_opportunities.py
index edfefb5e5bc..38f88b1c083 100644
--- a/addons/crm/wizard/crm_merge_opportunities.py
+++ b/addons/crm/wizard/crm_merge_opportunities.py
@@ -21,33 +21,51 @@ from osv import osv, fields
from tools.translate import _
class crm_merge_opportunity(osv.osv_memory):
- """Merge two Opportunities"""
+ """
+ Merge opportunities together.
+ If we're talking about opportunities, it's just because it makes more sense
+ to merge opps than leads, because the leads are more ephemeral objects.
+ But since opportunities are leads, it's also possible to merge leads
+ together (resulting in a new lead), or leads and opps together (resulting
+ in a new opp).
+ """
_name = 'crm.merge.opportunity'
- _description = 'Merge two Opportunities'
+ _description = 'Merge opportunities'
+ _columns = {
+ 'opportunity_ids': fields.many2many('crm.lead', rel='merge_opportunity_rel', id1='merge_id', id2='opportunity_id', string='Leads/Opportunities'),
+ }
def action_merge(self, cr, uid, ids, context=None):
if context is None:
context = {}
- lead = self.pool.get('crm.lead')
- record = self.browse(cr, uid, ids[0], context=context)
- opportunities = record.opportunity_ids
- #TOFIX: why need to check lead_ids here
- lead_ids = [opportunities[0].id]
- self.write(cr, uid, ids, {'opportunity_ids' : [(6,0, lead_ids)]}, context=context)
- context['lead_ids'] = lead_ids
- merge_id = lead.merge_opportunity(cr, uid, [x.id for x in opportunities], context=context)
- return lead.redirect_opportunity_view(cr, uid, merge_id, context=context)
- _columns = {
- 'opportunity_ids': fields.many2many('crm.lead', rel='merge_opportunity_rel', id1='merge_id', id2='opportunity_id', string='Leads/Opportunities'),
- }
+ lead_obj = self.pool.get('crm.lead')
+ wizard = self.browse(cr, uid, ids[0], context=context)
+ opportunity2merge_ids = wizard.opportunity_ids
+
+ #TODO: why is this passed through the context ?
+ context['lead_ids'] = [opportunity2merge_ids[0].id]
+
+ merge_id = lead_obj.merge_opportunity(cr, uid, [x.id for x in opportunity2merge_ids], context=context)
+
+ # The newly created lead might be a lead or an opp: redirect toward the right view
+ merge_result = lead_obj.browse(cr, uid, merge_id, context=context)
+
+ if merge_result.type == 'opportunity':
+ return lead_obj.redirect_opportunity_view(cr, uid, merge_id, context=context)
+ else:
+ return lead_obj.redirect_lead_view(cr, uid, merge_id, context=context)
def default_get(self, cr, uid, fields, context=None):
"""
- This function gets default values
+ Use active_ids from the context to fetch the leads/opps to merge.
+ In order to get merged, these leads/opps can't be in 'Done' or
+ 'Cancel' state.
"""
- record_ids = context and context.get('active_ids', False) or False
+ if context is None:
+ context = {}
+ record_ids = context.get('active_ids', False)
res = super(crm_merge_opportunity, self).default_get(cr, uid, fields, context=context)
if record_ids:
@@ -61,6 +79,4 @@ class crm_merge_opportunity(osv.osv_memory):
return res
-crm_merge_opportunity()
-
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/wizard/crm_partner_binding.py b/addons/crm/wizard/crm_partner_binding.py
new file mode 100644
index 00000000000..bc686db40f4
--- /dev/null
+++ b/addons/crm/wizard/crm_partner_binding.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from osv import osv, fields
+from tools.translate import _
+
+class crm_partner_binding(osv.osv_memory):
+ """
+ Handle the partner binding or generation in any CRM wizard that requires
+ such feature, like the lead2opportunity wizard, or the
+ phonecall2opportunity wizard. Try to find a matching partner from the
+ CRM model's information (name, email, phone number, etc) or create a new
+ one on the fly.
+ Use it like a mixin with the wizard of your choice.
+ """
+ _name = 'crm.partner.binding'
+ _description = 'Handle partner binding or generation in CRM wizards.'
+ _columns = {
+ 'action': fields.selection([
+ ('exist', 'Link to an existing customer'),
+ ('create', 'Create a new customer'),
+ ('nothing', 'Do not link to a customer')
+ ], 'Related Customer', required=True),
+ 'partner_id': fields.many2one('res.partner', 'Customer'),
+ }
+
+ def _find_matching_partner(self, cr, uid, context=None):
+ """
+ Try to find a matching partner regarding the active model data, like
+ the customer's name, email, phone number, etc.
+
+ :return int partner_id if any, False otherwise
+ """
+ if context is None:
+ context = {}
+ partner_id = False
+ partner_obj = self.pool.get('res.partner')
+
+ # The active model has to be a lead or a phonecall
+ if (context.get('active_model') == 'crm.lead') and context.get('active_id'):
+ active_model = self.pool.get('crm.lead').browse(cr, uid, context.get('active_id'), context=context)
+ elif (context.get('active_model') == 'crm.phonecall') and context.get('active_id'):
+ active_model = self.pool.get('crm.phonecall').browse(cr, uid, context.get('active_id'), context=context)
+
+ # Find the best matching partner for the active model
+ if (active_model):
+ partner_obj = self.pool.get('res.partner')
+
+ # A partner is set already
+ if active_model.partner_id:
+ partner_id = active_model.partner_id.id
+ # Search through the existing partners based on the lead's email
+ elif active_model.email_from:
+ partner_ids = partner_obj.search(cr, uid, [('email', '=', active_model.email_from)], context=context)
+ if partner_ids:
+ partner_id = partner_ids[0]
+ # Search through the existing partners based on the lead's partner or contact name
+ elif active_model.partner_name:
+ partner_ids = partner_obj.search(cr, uid, [('name', 'ilike', '%'+active_model.partner_name+'%')], context=context)
+ if partner_ids:
+ partner_id = partner_ids[0]
+ elif active_model.contact_name:
+ partner_ids = partner_obj.search(cr, uid, [
+ ('name', 'ilike', '%'+active_model.contact_name+'%')], context=context)
+ if partner_ids:
+ partner_id = partner_ids[0]
+
+ return partner_id
+
+ def default_get(self, cr, uid, fields, context=None):
+ res = super(crm_partner_binding, self).default_get(cr, uid, fields, context=context)
+ partner_id = self._find_matching_partner(cr, uid, context=context)
+
+ if 'action' in fields:
+ res['action'] = partner_id and 'exist' or 'create'
+ if 'partner_id' in fields:
+ res['partner_id'] = partner_id
+
+ return res
+
+ def _create_partner(self, cr, uid, ids, context=None):
+ """
+ Create partner based on action.
+ :return dict: dictionary organized as followed: {lead_id: partner_assigned_id}
+ """
+ #TODO this method in only called by crm_lead2opportunity_partner
+ #wizard and would probably diserve to be refactored or at least
+ #moved to a better place
+ if context is None:
+ context = {}
+ lead = self.pool.get('crm.lead')
+ lead_ids = context.get('active_ids', [])
+ data = self.browse(cr, uid, ids, context=context)[0]
+ partner_id = data.partner_id and data.partner_id.id or False
+ return lead.handle_partner_assignation(cr, uid, lead_ids, data.action, partner_id, context=context)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/wizard/crm_partner_to_opportunity.py b/addons/crm/wizard/crm_partner_to_opportunity.py
deleted file mode 100644
index aa8f4a2a036..00000000000
--- a/addons/crm/wizard/crm_partner_to_opportunity.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-from osv import osv, fields
-from tools.translate import _
-
-class crm_partner2opportunity(osv.osv_memory):
- """Converts Partner To Opportunity"""
-
- _name = 'crm.partner2opportunity'
- _description = 'Partner To Opportunity'
-
- _columns = {
- 'name' : fields.char('Opportunity Name', size=64, required=True),
- 'planned_revenue': fields.float('Expected Revenue', digits=(16,2)),
- 'probability': fields.float('Success Probability', digits=(16,2)),
- 'partner_id': fields.many2one('res.partner', 'Customer'),
- }
-
- def action_cancel(self, cr, uid, ids, context=None):
- """
- Closes Partner 2 Opportunity
- """
- return {'type':'ir.actions.act_window_close'}
-
- def default_get(self, cr, uid, fields, context=None):
- """
- This function gets default values
- """
- partner_obj = self.pool.get('res.partner')
- data = context and context.get('active_ids', []) or []
- res = super(crm_partner2opportunity, self).default_get(cr, uid, fields, context=context)
-
- for partner in partner_obj.browse(cr, uid, data, []):
- if 'name' in fields:
- res.update({'name': partner.name})
- if 'partner_id' in fields:
- res.update({'partner_id': data and data[0] or False})
- return res
-
- def make_opportunity(self, cr, uid, ids, context=None):
- partner_ids = context and context.get('active_ids', []) or []
- partner_id = partner_ids[0] if partner_ids else None
- partner = self.pool.get('res.partner')
- lead = self.pool.get('crm.lead')
- data = self.browse(cr, uid, ids, context=context)[0]
- opportunity_ids = partner.make_opportunity(cr, uid, partner_ids,
- data.name,
- data.planned_revenue,
- data.probability,
- partner_id,
- context=context,
- )
- opportunity_id = opportunity_ids[partner_ids[0]]
- return lead.redirect_opportunity_view(cr, uid, opportunity_id, context=context)
-
-crm_partner2opportunity()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/wizard/crm_partner_to_opportunity_view.xml b/addons/crm/wizard/crm_partner_to_opportunity_view.xml
deleted file mode 100644
index 7f0b5957071..00000000000
--- a/addons/crm/wizard/crm_partner_to_opportunity_view.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<openerp>
- <data>
-
- <!-- partner To Opportunity form -->
-
- <record id="view_crm_partner2opportunity" model="ir.ui.view">
- <field name="name">crm.crm.partner2opportunity</field>
- <field name="model">crm.partner2opportunity</field>
- <field name="arch" type="xml">
- <form string="Create Opportunity" version="7.0">
- <group col="4">
- <field name="name"/>
- <field name="partner_id"/>
- <field name="planned_revenue"/>
- <field name="probability"/>
- </group>
- <footer>
- <button name="make_opportunity" string="Create Opportunity" type="object" class="oe_highlight"/>
- or
- <button string="Cancel" class="oe_link" special="cancel" />
- </footer>
- </form>
- </field>
- </record>
-
- </data>
-</openerp>
diff --git a/addons/crm/wizard/crm_phonecall_to_opportunity.py b/addons/crm/wizard/crm_phonecall_to_opportunity.py
index 7305cbd8b5f..e69de29bb2d 100644
--- a/addons/crm/wizard/crm_phonecall_to_opportunity.py
+++ b/addons/crm/wizard/crm_phonecall_to_opportunity.py
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-from osv import osv, fields
-from tools.translate import _
-
-class crm_phonecall2opportunity(osv.osv_memory):
- """ Converts Phonecall to Opportunity"""
-
- _name = 'crm.phonecall2opportunity'
- _inherit = 'crm.partner2opportunity'
- _description = 'Phonecall To Opportunity'
-
- def make_opportunity(self, cr, uid, ids, context=None):
- """
- This converts Phonecall to Opportunity and opens Phonecall view
- """
- if not len(ids):
- return False
- call_ids = context and context.get('active_ids', False) or False
- this = self.browse(cr, uid, ids[0], context=context)
- if not call_ids:
- return {}
- opportunity = self.pool.get('crm.lead')
- phonecall = self.pool.get('crm.phonecall')
- opportunity_ids = phonecall.convert_opportunity(cr, uid, call_ids, this.name, this.partner_id and this.partner_id.id or False, \
- this.planned_revenue, this.probability, context=context)
- return opportunity.redirect_opportunity_view(cr, uid, opportunity_ids[call_ids[0]], context=context)
-
- def default_get(self, cr, uid, fields, context=None):
- """
- This function gets default values
- """
- record_id = context and context.get('active_id', False) or False
- res = {}
-
- if record_id:
- phonecall = self.pool.get('crm.phonecall').browse(cr, uid, record_id, context=context)
- if 'name' in fields:
- res.update({'name': phonecall.name})
- if 'partner_id' in fields:
- res.update({'partner_id': phonecall.partner_id and phonecall.partner_id.id or False})
- return res
-
-crm_phonecall2opportunity()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/wizard/crm_phonecall_to_opportunity_view.xml b/addons/crm/wizard/crm_phonecall_to_opportunity_view.xml
deleted file mode 100644
index 4b2ec725759..00000000000
--- a/addons/crm/wizard/crm_phonecall_to_opportunity_view.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0"?>
-<openerp>
- <data>
-
-<!-- Phonecall to Opportunity view -->
-
- <record model="ir.ui.view" id="phonecall2opportunity_view">
- <field name="name">crm.phonecall2opportunity.form</field>
- <field name="model">crm.phonecall2opportunity</field>
- <field name="arch" type="xml">
- <form string="Convert To Opportunity " version="7.0">
- <group>
- <field name="name"/>
- <field name="partner_id" />
- <label for="planned_revenue"/>
- <div>
- <field name="planned_revenue" class="oe_inline"/> at
- <field name="probability" class="oe_inline"/> %%
- </div>
- </group>
- <footer>
- <button name="make_opportunity" type="object" string="_Convert" class="oe_highlight"/>
- or
- <button string="Cancel" class="oe_link" special="cancel" />
- </footer>
- </form>
- </field>
- </record>
-
-<!-- Phonecall to Opportunity action -->
-
- <record model="ir.actions.act_window" id="phonecall2opportunity_act">
- <field name="name">Convert to opportunity</field>
- <field name="res_model">crm.phonecall2opportunity</field>
- <field name="view_type">form</field>
- <field name="view_mode">form</field>
- <field name="view_id" ref="phonecall2opportunity_view"/>
- <field name="target">new</field>
- </record>
-
- </data>
-</openerp>
diff --git a/addons/crm/wizard/crm_phonecall_to_partner.py b/addons/crm/wizard/crm_phonecall_to_partner.py
deleted file mode 100644
index 715b7396c66..00000000000
--- a/addons/crm/wizard/crm_phonecall_to_partner.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-from osv import osv, fields
-from tools.translate import _
-
-class crm_phonecall2partner(osv.osv_memory):
- """ Converts phonecall to partner """
-
- _name = 'crm.phonecall2partner'
- _inherit = 'crm.lead2partner'
- _description = 'Phonecall to Partner'
-
- def _select_partner(self, cr, uid, context=None):
- """
- This function Searches for Partner from selected phonecall.
- """
- if context is None:
- context = {}
-
- phonecall_obj = self.pool.get('crm.phonecall')
- partner_obj = self.pool.get('res.partner')
- rec_ids = context and context.get('active_ids', [])
- value = {}
- partner_id = False
- for phonecall in phonecall_obj.browse(cr, uid, rec_ids, context=context):
- partner_ids = partner_obj.search(cr, uid, [('name', '=', phonecall.name or phonecall.name)])
- if not partner_ids and (phonecall.partner_phone or phonecall.partner_mobile):
- partner_ids = partner_obj.search(cr, uid, ['|', ('phone', '=', phonecall.partner_phone), ('mobile','=',phonecall.partner_mobile)])
-
- partner_id = partner_ids and partner_ids[0] or False
- return partner_id
-
-
- def _create_partner(self, cr, uid, ids, context=None):
- """
- This function Creates partner based on action.
- """
- if context is None:
- context = {}
- phonecall = self.pool.get('crm.phonecall')
- data = self.browse(cr, uid, ids, context=context)[0]
- call_ids = context and context.get('active_ids') or []
- partner_id = data.partner_id and data.partner_id.id or False
- return phonecall.convert_partner(cr, uid, call_ids, data.action, partner_id, context=context)
-
-crm_phonecall2partner()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/wizard/crm_phonecall_to_partner_view.xml b/addons/crm/wizard/crm_phonecall_to_partner_view.xml
deleted file mode 100644
index 7231876b0c1..00000000000
--- a/addons/crm/wizard/crm_phonecall_to_partner_view.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<openerp>
- <data>
-
- <!-- Phonecall to partner confirmation form -->
-
- <record id="view_crm_phonecall2partner_create" model="ir.ui.view">
- <field name="name">crm.phonecall2partner.view.create</field>
- <field name="model">crm.phonecall2partner</field>
- <field name="arch" type="xml">
- <form string="Create a Partner" version="7.0">
- <label string="Are you sure you want to create a partner based on this Phonecall ?"/>
- <newline/>
- <label string="You may have to verify that this partner does not exist already."/>
- <footer>
- <button name="open_create_partner" string="Create Partner" type="object" groups="base.group_partner_manager" class="oe_highlight"/>
- or
- <button string="Cancel" class="oe_link" special="cancel" />
- </footer>
- </form>
- </field>
- </record>
-
- <!-- Phonecall to partner form view -->
-
- <record id="view_crm_phonecall2partner" model="ir.ui.view">
- <field name="name">crm.phonecall2partner.view</field>
- <field name="model">crm.phonecall2partner</field>
- <field name="arch" type="xml">
- <form string="Create a Partner" version="7.0">
- <group>
- <field name="action"/>
- <field name="partner_id" attrs="{'required':[('action','=','exist')],'invisible':[('action','!=','exist')]}" />
- </group>
- <footer>
- <button name="make_partner" string="Continue" type="object" class="oe_highlight"/>
- or
- <button string="Cancel" class="oe_link" special="cancel" />
- </footer>
- </form>
- </field>
- </record>
-
- <!-- Phonecall to Partner wizard -->
-
- <record id="action_crm_phonecall2partner" model="ir.actions.act_window">
- <field name="name">Create a Partner</field>
- <field name="type">ir.actions.act_window</field>
- <field name="res_model">crm.phonecall2partner</field>
- <field name="view_type">form</field>
- <field name="view_id" ref="view_crm_phonecall2partner_create"/>
- <field name="target">new</field>
- </record>
- </data>
-</openerp>