Merge lp:~openerp-dev/openobject-addons/trunk-massassign-dle into lp:openobject-addons

Proposed by Denis Ledoux (OpenERP)
Status: Work in progress
Proposed branch: lp:~openerp-dev/openobject-addons/trunk-massassign-dle
Merge into: lp:openobject-addons
Diff against target: 281 lines (+98/-58)
4 files modified
crm/crm_lead.py (+8/-23)
crm/test/lead2opportunity_assign_salesmen.yml (+1/-1)
crm/wizard/crm_lead_to_opportunity.py (+81/-22)
crm/wizard/crm_lead_to_opportunity_view.xml (+8/-12)
To merge this branch: bzr merge lp:~openerp-dev/openobject-addons/trunk-massassign-dle
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+202903@code.launchpad.net
To post a comment you must log in.

Unmerged revisions

9025. By Denis Ledoux (OpenERP)

[ADD] crm: mass assign apply deduplication

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'crm/crm_lead.py'
--- crm/crm_lead.py 2014-01-15 21:44:54 +0000
+++ crm/crm_lead.py 2014-01-23 17:37:05 +0000
@@ -761,24 +761,6 @@
761 )761 )
762 return partner_id762 return partner_id
763763
764 def _lead_set_partner(self, cr, uid, lead, partner_id, context=None):
765 """
766 Assign a partner to a lead.
767
768 :param object lead: browse record of the lead to process
769 :param int partner_id: identifier of the partner to assign
770 :return bool: True if the partner has properly been assigned
771 """
772 res = False
773 res_partner = self.pool.get('res.partner')
774 if partner_id:
775 res_partner.write(cr, uid, partner_id, {'section_id': lead.section_id and lead.section_id.id or False})
776 contact_id = res_partner.address_get(cr, uid, [partner_id])['default']
777 res = lead.write({'partner_id': partner_id}, context=context)
778 message = _("<b>Partner</b> set to <em>%s</em>." % (lead.partner_id.name))
779 self.message_post(cr, uid, [lead.id], body=message, context=context)
780 return res
781
782 def handle_partner_assignation(self, cr, uid, ids, action='create', partner_id=False, context=None):764 def handle_partner_assignation(self, cr, uid, ids, action='create', partner_id=False, context=None):
783 """765 """
784 Handle partner assignation during a lead conversion.766 Handle partner assignation during a lead conversion.
@@ -792,13 +774,16 @@
792 """774 """
793 #TODO this is a duplication of the handle_partner_assignation method of crm_phonecall775 #TODO this is a duplication of the handle_partner_assignation method of crm_phonecall
794 partner_ids = {}776 partner_ids = {}
795 # If a partner_id is given, force this partner for all elements
796 force_partner_id = partner_id
797 for lead in self.browse(cr, uid, ids, context=context):777 for lead in self.browse(cr, uid, ids, context=context):
798 # If the action is set to 'create' and no partner_id is set, create a new one778 # If the action is set to 'create' and no partner_id is set, create a new one
799 if action == 'create':779 if lead.partner_id:
800 partner_id = force_partner_id or self._create_lead_partner(cr, uid, lead, context)780 partner_ids[lead.id] = lead.partner_id.id
801 self._lead_set_partner(cr, uid, lead, partner_id, context=context)781 continue
782 if not partner_id and action == 'create':
783 partner_id = self._create_lead_partner(cr, uid, lead, context)
784 self.pool['res.partner'].write(cr, uid, partner_id, {'section_id': lead.section_id and lead.section_id.id or False})
785 if partner_id:
786 lead.write({'partner_id': partner_id}, context=context)
802 partner_ids[lead.id] = partner_id787 partner_ids[lead.id] = partner_id
803 return partner_ids788 return partner_ids
804789
805790
=== modified file 'crm/test/lead2opportunity_assign_salesmen.yml'
--- crm/test/lead2opportunity_assign_salesmen.yml 2013-10-27 12:31:04 +0000
+++ crm/test/lead2opportunity_assign_salesmen.yml 2014-01-23 17:37:05 +0000
@@ -66,7 +66,7 @@
66-66-
67 !python {model: crm.lead2opportunity.partner.mass}: |67 !python {model: crm.lead2opportunity.partner.mass}: |
68 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")})68 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")})
69 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)69 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'), 'deduplicate': False}, context=context)
70 self.mass_convert(cr, uid, [id], context=context)70 self.mass_convert(cr, uid, [id], context=context)
71-71-
72 The leads should now be opps with a salesman and a salesteam. Also, salesmen should have been assigned following a round-robin method.72 The leads should now be opps with a salesman and a salesteam. Also, salesmen should have been assigned following a round-robin method.
7373
=== modified file 'crm/wizard/crm_lead_to_opportunity.py'
--- crm/wizard/crm_lead_to_opportunity.py 2013-11-30 13:07:25 +0000
+++ crm/wizard/crm_lead_to_opportunity.py 2014-01-23 17:37:05 +0000
@@ -41,6 +41,22 @@
41 def onchange_action(self, cr, uid, ids, action, context=None):41 def onchange_action(self, cr, uid, ids, action, context=None):
42 return {'value': {'partner_id': False if action != 'exist' else self._find_matching_partner(cr, uid, context=context)}}42 return {'value': {'partner_id': False if action != 'exist' else self._find_matching_partner(cr, uid, context=context)}}
4343
44 def _get_duplicated_leads(self, cr, uid, partner_id, email, context=None):
45 lead_obj = self.pool.get('crm.lead')
46 results = []
47 if partner_id:
48 # Search for opportunities that have the same partner and that arent done or cancelled
49 ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('probability', '=', False), ('probability', '<', '100')])
50 for id in ids:
51 results.append(id)
52 email = re.findall(r'([^ ,<@]+@[^> ,]+)', email or '')
53 if email:
54 ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('probability', '=', False), ('probability', '<', '100')])
55 for id in ids:
56 results.append(id)
57 return list(set(results))
58
59
44 def default_get(self, cr, uid, fields, context=None):60 def default_get(self, cr, uid, fields, context=None):
45 """61 """
46 Default get for name, opportunity_ids.62 Default get for name, opportunity_ids.
@@ -51,24 +67,15 @@
5167
52 res = super(crm_lead2opportunity_partner, self).default_get(cr, uid, fields, context=context)68 res = super(crm_lead2opportunity_partner, self).default_get(cr, uid, fields, context=context)
53 if context.get('active_id'):69 if context.get('active_id'):
54 tomerge = set([int(context['active_id'])])70 tomerge = [int(context['active_id'])]
5571
56 email = False72 email = False
57 partner_id = res.get('partner_id')73 partner_id = res.get('partner_id')
58 lead = lead_obj.browse(cr, uid, int(context['active_id']), context=context)74 lead = lead_obj.browse(cr, uid, int(context['active_id']), context=context)
5975
60 #TOFIX: use mail.mail_message.to_mail76 #TOFIX: use mail.mail_message.to_mail
61 email = re.findall(r'([^ ,<@]+@[^> ,]+)', lead.email_from or '')77 tomerge.extend(self._get_duplicated_leads(cr, uid, partner_id, email))
6278 tomerge = list(set(tomerge))
63 if partner_id:
64 # Search for opportunities that have the same partner and that arent done or cancelled
65 ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('probability', '=', False), ('probability', '<', '100')])
66 for id in ids:
67 tomerge.add(id)
68 if email:
69 ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('probability', '=', False), ('probability', '<', '100')])
70 for id in ids:
71 tomerge.add(id)
7279
73 if 'action' in fields:80 if 'action' in fields:
74 res.update({'action' : partner_id and 'exist' or 'create'})81 res.update({'action' : partner_id and 'exist' or 'create'})
@@ -77,7 +84,7 @@
77 if 'name' in fields:84 if 'name' in fields:
78 res.update({'name' : len(tomerge) >= 2 and 'merge' or 'convert'})85 res.update({'name' : len(tomerge) >= 2 and 'merge' or 'convert'})
79 if 'opportunity_ids' in fields and len(tomerge) >= 2:86 if 'opportunity_ids' in fields and len(tomerge) >= 2:
80 res.update({'opportunity_ids': list(tomerge)})87 res.update({'opportunity_ids': tomerge})
81 if lead.user_id:88 if lead.user_id:
82 res.update({'user_id': lead.user_id.id})89 res.update({'user_id': lead.user_id.id})
83 if lead.section_id:90 if lead.section_id:
@@ -116,11 +123,11 @@
116 context = {}123 context = {}
117 lead = self.pool.get('crm.lead')124 lead = self.pool.get('crm.lead')
118 res = False125 res = False
119 partner_ids_map = self._create_partner(cr, uid, ids, context=context)
120 lead_ids = vals.get('lead_ids', [])126 lead_ids = vals.get('lead_ids', [])
121 team_id = vals.get('section_id', False)127 team_id = vals.get('section_id', False)
128 data = self.browse(cr, uid, ids, context=context)[0]
122 for lead_id in lead_ids:129 for lead_id in lead_ids:
123 partner_id = partner_ids_map.get(lead_id, False)130 partner_id = self._create_partner(cr, uid, lead_id, data.action, data.partner_id, context=context)
124 # FIXME: cannot pass user_ids as the salesman allocation only works in batch131 # FIXME: cannot pass user_ids as the salesman allocation only works in batch
125 res = lead.convert_opportunity(cr, uid, [lead_id], partner_id, [], team_id, context=context)132 res = lead.convert_opportunity(cr, uid, [lead_id], partner_id, [], team_id, context=context)
126 # FIXME: must perform salesman allocation in batch separately here133 # FIXME: must perform salesman allocation in batch separately here
@@ -152,7 +159,7 @@
152159
153 return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)160 return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
154161
155 def _create_partner(self, cr, uid, ids, context=None):162 def _create_partner(self, cr, uid, lead_id, action, partner_id, context=None):
156 """163 """
157 Create partner based on action.164 Create partner based on action.
158 :return dict: dictionary organized as followed: {lead_id: partner_assigned_id}165 :return dict: dictionary organized as followed: {lead_id: partner_assigned_id}
@@ -163,10 +170,14 @@
163 if context is None:170 if context is None:
164 context = {}171 context = {}
165 lead = self.pool.get('crm.lead')172 lead = self.pool.get('crm.lead')
166 lead_ids = context.get('active_ids', [])173 if action == 'each_exist_or_create':
167 data = self.browse(cr, uid, ids, context=context)[0]174 ctx = dict(context)
168 partner_id = data.partner_id and data.partner_id.id or False175 ctx['active_id'] = lead_id
169 return lead.handle_partner_assignation(cr, uid, lead_ids, data.action, partner_id, context=context)176 partner_id = self._find_matching_partner(cr, uid, context=ctx)
177 action = 'create'
178 print partner_id
179 res = lead.handle_partner_assignation(cr, uid, [lead_id], action, partner_id, context=context)
180 return res.get(lead_id)
170181
171class crm_lead2opportunity_mass_convert(osv.osv_memory):182class crm_lead2opportunity_mass_convert(osv.osv_memory):
172 _name = 'crm.lead2opportunity.partner.mass'183 _name = 'crm.lead2opportunity.partner.mass'
@@ -176,6 +187,15 @@
176 _columns = {187 _columns = {
177 'user_ids': fields.many2many('res.users', string='Salesmen'),188 'user_ids': fields.many2many('res.users', string='Salesmen'),
178 'section_id': fields.many2one('crm.case.section', 'Sales Team'),189 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
190 'deduplicate': fields.boolean('Apply deduplication', help='Merge with existing leads/opportunities of each partner'),
191 'action': fields.selection([
192 ('each_exist_or_create', 'Use existing partner or create'),
193 ('nothing', 'Do not link to a customer')
194 ], 'Related Customer', required=True),
195 }
196
197 _defaults = {
198 'deduplicate': True,
179 }199 }
180200
181 def default_get(self, cr, uid, fields, context=None):201 def default_get(self, cr, uid, fields, context=None):
@@ -184,13 +204,37 @@
184 # avoid forcing the partner of the first lead as default204 # avoid forcing the partner of the first lead as default
185 res['partner_id'] = False205 res['partner_id'] = False
186 if 'action' in fields:206 if 'action' in fields:
187 res['action'] = 'create'207 res['action'] = 'each_exist_or_create'
188 if 'name' in fields:208 if 'name' in fields:
189 res['name'] = 'convert'209 res['name'] = 'convert'
190 if 'opportunity_ids' in fields:210 if 'opportunity_ids' in fields:
191 res['opportunity_ids'] = False211 res['opportunity_ids'] = False
192 return res212 return res
193213
214 def on_change_action(self, cr, uid, ids, action, context=None):
215 vals = {}
216 if action != 'exist':
217 vals = {'value': {'partner_id': False}}
218 return vals
219
220 def on_change_deduplicate(self, cr, uid, ids, deduplicate, context=None):
221 if context is None:
222 context = {}
223 active_leads = self.pool['crm.lead'].browse(cr, uid, context['active_ids'], context=context)
224 partner_ids = [(lead.partner_id.id, lead.partner_id and lead.partner_id.email or lead.email_from) for lead in active_leads]
225 partners_duplicated_leads = {}
226 for partner_id, email in partner_ids:
227 duplicated_leads = self._get_duplicated_leads(cr, uid, partner_id, email)
228 if len(duplicated_leads) > 1:
229 partners_duplicated_leads.setdefault((partner_id, email), []).extend(duplicated_leads)
230 leads_with_duplicates = []
231 for lead in active_leads:
232 lead_tuple = (lead.partner_id.id, lead.partner_id.email if lead.partner_id else lead.email_from)
233 if len(partners_duplicated_leads.get(lead_tuple, [])) > 1:
234 leads_with_duplicates.append(lead.id)
235 return {'value': {'opportunity_ids': leads_with_duplicates}}
236
237
194 def _convert_opportunity(self, cr, uid, ids, vals, context=None):238 def _convert_opportunity(self, cr, uid, ids, vals, context=None):
195 """239 """
196 When "massively" (more than one at a time) converting leads to240 When "massively" (more than one at a time) converting leads to
@@ -208,6 +252,21 @@
208 return super(crm_lead2opportunity_mass_convert, self)._convert_opportunity(cr, uid, ids, vals, context=context)252 return super(crm_lead2opportunity_mass_convert, self)._convert_opportunity(cr, uid, ids, vals, context=context)
209253
210 def mass_convert(self, cr, uid, ids, context=None):254 def mass_convert(self, cr, uid, ids, context=None):
211 return self.action_apply(cr, uid, ids, context=context)255 data = self.browse(cr, uid, ids, context=context)[0]
256 ctx = dict(context)
257 if data.name == 'convert' and data.deduplicate:
258 merged_lead_ids = []
259 remaining_lead_ids = []
260 for lead in data.opportunity_ids:
261 duplicated_lead_ids = self._get_duplicated_leads(cr, uid, lead.partner_id.id, lead.partner_id and lead.partner_id.email or lead.email_from)
262 if len(duplicated_lead_ids) > 1:
263 lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, duplicated_lead_ids, False, False, context=context)
264 merged_lead_ids.extend(duplicated_lead_ids)
265 remaining_lead_ids.append(lead_id)
266 active_ids = set(context.get('active_ids', []))
267 active_ids = active_ids.difference(merged_lead_ids)
268 active_ids = active_ids.union(remaining_lead_ids)
269 ctx['active_ids'] = list(active_ids)
270 return self.action_apply(cr, uid, ids, context=ctx)
212271
213# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:272# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
214273
=== modified file 'crm/wizard/crm_lead_to_opportunity_view.xml'
--- crm/wizard/crm_lead_to_opportunity_view.xml 2013-11-30 13:07:25 +0000
+++ crm/wizard/crm_lead_to_opportunity_view.xml 2014-01-23 17:37:05 +0000
@@ -52,21 +52,17 @@
52 <form string="Convert to Opportunity" version="7.0">52 <form string="Convert to Opportunity" version="7.0">
53 <separator string="Conversion Options"/>53 <separator string="Conversion Options"/>
54 <group>54 <group>
55 <field name="name" class="oe_inline"/>55 <field name="action" class="oe_inline" on_change="on_change_action(action)"/>
56 </group>56 <field name="deduplicate" class="oe_inline" on_change="on_change_deduplicate(deduplicate, context)"/>
57 <group attrs="{'invisible': [('name', '!=', 'convert')]}">57 </group>
58 <field name="action" class="oe_inline"/>58 <group string="Assign opportunities to">
59 <field name="partner_id"
60 attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"
61 class="oe_inline"/>
62 </group>
63 <group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}">
64 <field name="section_id" groups="base.group_multi_salesteams"/>59 <field name="section_id" groups="base.group_multi_salesteams"/>
65 <field name="user_ids" widget="many2many_tags"/>60 <field name="user_ids" widget="many2many_tags"/>
66 </group>61 </group>
67 <group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">62 <label for="opportunity_ids" string="Leads with existing duplicates (for information)" help="Leads that you selected that have duplicates. If the list is empty, it means that no duplicates were found"/>
68 <field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">63 <group attrs="{'invisible': [('deduplicate', '=', False)]}">
69 <tree>64 <field name="opportunity_ids" colspan="4" nolabel="1">
65 <tree create="false" delete="false">
70 <field name="create_date"/>66 <field name="create_date"/>
71 <field name="name"/>67 <field name="name"/>
72 <field name="type"/>68 <field name="type"/>

Subscribers

People subscribed via source and target branches

to all changes: