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
1=== modified file 'crm/crm_lead.py'
2--- crm/crm_lead.py 2014-01-15 21:44:54 +0000
3+++ crm/crm_lead.py 2014-01-23 17:37:05 +0000
4@@ -761,24 +761,6 @@
5 )
6 return partner_id
7
8- def _lead_set_partner(self, cr, uid, lead, partner_id, context=None):
9- """
10- Assign a partner to a lead.
11-
12- :param object lead: browse record of the lead to process
13- :param int partner_id: identifier of the partner to assign
14- :return bool: True if the partner has properly been assigned
15- """
16- res = False
17- res_partner = self.pool.get('res.partner')
18- if partner_id:
19- res_partner.write(cr, uid, partner_id, {'section_id': lead.section_id and lead.section_id.id or False})
20- contact_id = res_partner.address_get(cr, uid, [partner_id])['default']
21- res = lead.write({'partner_id': partner_id}, context=context)
22- message = _("<b>Partner</b> set to <em>%s</em>." % (lead.partner_id.name))
23- self.message_post(cr, uid, [lead.id], body=message, context=context)
24- return res
25-
26 def handle_partner_assignation(self, cr, uid, ids, action='create', partner_id=False, context=None):
27 """
28 Handle partner assignation during a lead conversion.
29@@ -792,13 +774,16 @@
30 """
31 #TODO this is a duplication of the handle_partner_assignation method of crm_phonecall
32 partner_ids = {}
33- # If a partner_id is given, force this partner for all elements
34- force_partner_id = partner_id
35 for lead in self.browse(cr, uid, ids, context=context):
36 # If the action is set to 'create' and no partner_id is set, create a new one
37- if action == 'create':
38- partner_id = force_partner_id or self._create_lead_partner(cr, uid, lead, context)
39- self._lead_set_partner(cr, uid, lead, partner_id, context=context)
40+ if lead.partner_id:
41+ partner_ids[lead.id] = lead.partner_id.id
42+ continue
43+ if not partner_id and action == 'create':
44+ partner_id = self._create_lead_partner(cr, uid, lead, context)
45+ self.pool['res.partner'].write(cr, uid, partner_id, {'section_id': lead.section_id and lead.section_id.id or False})
46+ if partner_id:
47+ lead.write({'partner_id': partner_id}, context=context)
48 partner_ids[lead.id] = partner_id
49 return partner_ids
50
51
52=== modified file 'crm/test/lead2opportunity_assign_salesmen.yml'
53--- crm/test/lead2opportunity_assign_salesmen.yml 2013-10-27 12:31:04 +0000
54+++ crm/test/lead2opportunity_assign_salesmen.yml 2014-01-23 17:37:05 +0000
55@@ -66,7 +66,7 @@
56 -
57 !python {model: crm.lead2opportunity.partner.mass}: |
58 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")})
59- 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)
60+ 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)
61 self.mass_convert(cr, uid, [id], context=context)
62 -
63 The leads should now be opps with a salesman and a salesteam. Also, salesmen should have been assigned following a round-robin method.
64
65=== modified file 'crm/wizard/crm_lead_to_opportunity.py'
66--- crm/wizard/crm_lead_to_opportunity.py 2013-11-30 13:07:25 +0000
67+++ crm/wizard/crm_lead_to_opportunity.py 2014-01-23 17:37:05 +0000
68@@ -41,6 +41,22 @@
69 def onchange_action(self, cr, uid, ids, action, context=None):
70 return {'value': {'partner_id': False if action != 'exist' else self._find_matching_partner(cr, uid, context=context)}}
71
72+ def _get_duplicated_leads(self, cr, uid, partner_id, email, context=None):
73+ lead_obj = self.pool.get('crm.lead')
74+ results = []
75+ if partner_id:
76+ # Search for opportunities that have the same partner and that arent done or cancelled
77+ ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('probability', '=', False), ('probability', '<', '100')])
78+ for id in ids:
79+ results.append(id)
80+ email = re.findall(r'([^ ,<@]+@[^> ,]+)', email or '')
81+ if email:
82+ ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('probability', '=', False), ('probability', '<', '100')])
83+ for id in ids:
84+ results.append(id)
85+ return list(set(results))
86+
87+
88 def default_get(self, cr, uid, fields, context=None):
89 """
90 Default get for name, opportunity_ids.
91@@ -51,24 +67,15 @@
92
93 res = super(crm_lead2opportunity_partner, self).default_get(cr, uid, fields, context=context)
94 if context.get('active_id'):
95- tomerge = set([int(context['active_id'])])
96+ tomerge = [int(context['active_id'])]
97
98 email = False
99 partner_id = res.get('partner_id')
100 lead = lead_obj.browse(cr, uid, int(context['active_id']), context=context)
101
102 #TOFIX: use mail.mail_message.to_mail
103- email = re.findall(r'([^ ,<@]+@[^> ,]+)', lead.email_from or '')
104-
105- if partner_id:
106- # Search for opportunities that have the same partner and that arent done or cancelled
107- ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('probability', '=', False), ('probability', '<', '100')])
108- for id in ids:
109- tomerge.add(id)
110- if email:
111- ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('probability', '=', False), ('probability', '<', '100')])
112- for id in ids:
113- tomerge.add(id)
114+ tomerge.extend(self._get_duplicated_leads(cr, uid, partner_id, email))
115+ tomerge = list(set(tomerge))
116
117 if 'action' in fields:
118 res.update({'action' : partner_id and 'exist' or 'create'})
119@@ -77,7 +84,7 @@
120 if 'name' in fields:
121 res.update({'name' : len(tomerge) >= 2 and 'merge' or 'convert'})
122 if 'opportunity_ids' in fields and len(tomerge) >= 2:
123- res.update({'opportunity_ids': list(tomerge)})
124+ res.update({'opportunity_ids': tomerge})
125 if lead.user_id:
126 res.update({'user_id': lead.user_id.id})
127 if lead.section_id:
128@@ -116,11 +123,11 @@
129 context = {}
130 lead = self.pool.get('crm.lead')
131 res = False
132- partner_ids_map = self._create_partner(cr, uid, ids, context=context)
133 lead_ids = vals.get('lead_ids', [])
134 team_id = vals.get('section_id', False)
135+ data = self.browse(cr, uid, ids, context=context)[0]
136 for lead_id in lead_ids:
137- partner_id = partner_ids_map.get(lead_id, False)
138+ partner_id = self._create_partner(cr, uid, lead_id, data.action, data.partner_id, context=context)
139 # FIXME: cannot pass user_ids as the salesman allocation only works in batch
140 res = lead.convert_opportunity(cr, uid, [lead_id], partner_id, [], team_id, context=context)
141 # FIXME: must perform salesman allocation in batch separately here
142@@ -152,7 +159,7 @@
143
144 return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
145
146- def _create_partner(self, cr, uid, ids, context=None):
147+ def _create_partner(self, cr, uid, lead_id, action, partner_id, context=None):
148 """
149 Create partner based on action.
150 :return dict: dictionary organized as followed: {lead_id: partner_assigned_id}
151@@ -163,10 +170,14 @@
152 if context is None:
153 context = {}
154 lead = self.pool.get('crm.lead')
155- lead_ids = context.get('active_ids', [])
156- data = self.browse(cr, uid, ids, context=context)[0]
157- partner_id = data.partner_id and data.partner_id.id or False
158- return lead.handle_partner_assignation(cr, uid, lead_ids, data.action, partner_id, context=context)
159+ if action == 'each_exist_or_create':
160+ ctx = dict(context)
161+ ctx['active_id'] = lead_id
162+ partner_id = self._find_matching_partner(cr, uid, context=ctx)
163+ action = 'create'
164+ print partner_id
165+ res = lead.handle_partner_assignation(cr, uid, [lead_id], action, partner_id, context=context)
166+ return res.get(lead_id)
167
168 class crm_lead2opportunity_mass_convert(osv.osv_memory):
169 _name = 'crm.lead2opportunity.partner.mass'
170@@ -176,6 +187,15 @@
171 _columns = {
172 'user_ids': fields.many2many('res.users', string='Salesmen'),
173 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
174+ 'deduplicate': fields.boolean('Apply deduplication', help='Merge with existing leads/opportunities of each partner'),
175+ 'action': fields.selection([
176+ ('each_exist_or_create', 'Use existing partner or create'),
177+ ('nothing', 'Do not link to a customer')
178+ ], 'Related Customer', required=True),
179+ }
180+
181+ _defaults = {
182+ 'deduplicate': True,
183 }
184
185 def default_get(self, cr, uid, fields, context=None):
186@@ -184,13 +204,37 @@
187 # avoid forcing the partner of the first lead as default
188 res['partner_id'] = False
189 if 'action' in fields:
190- res['action'] = 'create'
191+ res['action'] = 'each_exist_or_create'
192 if 'name' in fields:
193 res['name'] = 'convert'
194 if 'opportunity_ids' in fields:
195 res['opportunity_ids'] = False
196 return res
197
198+ def on_change_action(self, cr, uid, ids, action, context=None):
199+ vals = {}
200+ if action != 'exist':
201+ vals = {'value': {'partner_id': False}}
202+ return vals
203+
204+ def on_change_deduplicate(self, cr, uid, ids, deduplicate, context=None):
205+ if context is None:
206+ context = {}
207+ active_leads = self.pool['crm.lead'].browse(cr, uid, context['active_ids'], context=context)
208+ partner_ids = [(lead.partner_id.id, lead.partner_id and lead.partner_id.email or lead.email_from) for lead in active_leads]
209+ partners_duplicated_leads = {}
210+ for partner_id, email in partner_ids:
211+ duplicated_leads = self._get_duplicated_leads(cr, uid, partner_id, email)
212+ if len(duplicated_leads) > 1:
213+ partners_duplicated_leads.setdefault((partner_id, email), []).extend(duplicated_leads)
214+ leads_with_duplicates = []
215+ for lead in active_leads:
216+ lead_tuple = (lead.partner_id.id, lead.partner_id.email if lead.partner_id else lead.email_from)
217+ if len(partners_duplicated_leads.get(lead_tuple, [])) > 1:
218+ leads_with_duplicates.append(lead.id)
219+ return {'value': {'opportunity_ids': leads_with_duplicates}}
220+
221+
222 def _convert_opportunity(self, cr, uid, ids, vals, context=None):
223 """
224 When "massively" (more than one at a time) converting leads to
225@@ -208,6 +252,21 @@
226 return super(crm_lead2opportunity_mass_convert, self)._convert_opportunity(cr, uid, ids, vals, context=context)
227
228 def mass_convert(self, cr, uid, ids, context=None):
229- return self.action_apply(cr, uid, ids, context=context)
230+ data = self.browse(cr, uid, ids, context=context)[0]
231+ ctx = dict(context)
232+ if data.name == 'convert' and data.deduplicate:
233+ merged_lead_ids = []
234+ remaining_lead_ids = []
235+ for lead in data.opportunity_ids:
236+ 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)
237+ if len(duplicated_lead_ids) > 1:
238+ lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, duplicated_lead_ids, False, False, context=context)
239+ merged_lead_ids.extend(duplicated_lead_ids)
240+ remaining_lead_ids.append(lead_id)
241+ active_ids = set(context.get('active_ids', []))
242+ active_ids = active_ids.difference(merged_lead_ids)
243+ active_ids = active_ids.union(remaining_lead_ids)
244+ ctx['active_ids'] = list(active_ids)
245+ return self.action_apply(cr, uid, ids, context=ctx)
246
247 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
248
249=== modified file 'crm/wizard/crm_lead_to_opportunity_view.xml'
250--- crm/wizard/crm_lead_to_opportunity_view.xml 2013-11-30 13:07:25 +0000
251+++ crm/wizard/crm_lead_to_opportunity_view.xml 2014-01-23 17:37:05 +0000
252@@ -52,21 +52,17 @@
253 <form string="Convert to Opportunity" version="7.0">
254 <separator string="Conversion Options"/>
255 <group>
256- <field name="name" class="oe_inline"/>
257- </group>
258- <group attrs="{'invisible': [('name', '!=', 'convert')]}">
259- <field name="action" class="oe_inline"/>
260- <field name="partner_id"
261- attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"
262- class="oe_inline"/>
263- </group>
264- <group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}">
265+ <field name="action" class="oe_inline" on_change="on_change_action(action)"/>
266+ <field name="deduplicate" class="oe_inline" on_change="on_change_deduplicate(deduplicate, context)"/>
267+ </group>
268+ <group string="Assign opportunities to">
269 <field name="section_id" groups="base.group_multi_salesteams"/>
270 <field name="user_ids" widget="many2many_tags"/>
271 </group>
272- <group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
273- <field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">
274- <tree>
275+ <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"/>
276+ <group attrs="{'invisible': [('deduplicate', '=', False)]}">
277+ <field name="opportunity_ids" colspan="4" nolabel="1">
278+ <tree create="false" delete="false">
279 <field name="create_date"/>
280 <field name="name"/>
281 <field name="type"/>

Subscribers

People subscribed via source and target branches

to all changes: