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