Merge lp:~openerp-dev/openobject-addons/trunk-massassign-dle into lp:openobject-addons
- trunk-massassign-dle
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenERP Core Team | Pending | ||
Review via email: mp+202903@code.launchpad.net |
Commit message
Description of the change
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"/> |