base_partner_merge.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from ast import literal_eval
  4. from collections import defaultdict
  5. import functools
  6. import itertools
  7. import logging
  8. import psycopg2
  9. import datetime
  10. from odoo import api, fields, models, Command
  11. from odoo import SUPERUSER_ID, _
  12. from odoo.exceptions import ValidationError, UserError
  13. from odoo.tools import mute_logger
  14. _logger = logging.getLogger('odoo.addons.base.partner.merge')
  15. class MergePartnerLine(models.TransientModel):
  16. _name = 'base.partner.merge.line'
  17. _description = 'Merge Partner Line'
  18. _order = 'min_id asc'
  19. wizard_id = fields.Many2one('base.partner.merge.automatic.wizard', 'Wizard')
  20. min_id = fields.Integer('MinID')
  21. aggr_ids = fields.Char('Ids', required=True)
  22. class MergePartnerAutomatic(models.TransientModel):
  23. """
  24. The idea behind this wizard is to create a list of potential partners to
  25. merge. We use two objects, the first one is the wizard for the end-user.
  26. And the second will contain the partner list to merge.
  27. """
  28. _name = 'base.partner.merge.automatic.wizard'
  29. _description = 'Merge Partner Wizard'
  30. @api.model
  31. def default_get(self, fields):
  32. res = super(MergePartnerAutomatic, self).default_get(fields)
  33. active_ids = self.env.context.get('active_ids')
  34. if self.env.context.get('active_model') == 'res.partner' and active_ids:
  35. if 'state' in fields:
  36. res['state'] = 'selection'
  37. if 'partner_ids' in fields:
  38. res['partner_ids'] = [Command.set(active_ids)]
  39. if 'dst_partner_id' in fields:
  40. res['dst_partner_id'] = self._get_ordered_partner(active_ids)[-1].id
  41. return res
  42. # Group by
  43. group_by_email = fields.Boolean('Email')
  44. group_by_name = fields.Boolean('Name')
  45. group_by_is_company = fields.Boolean('Is Company')
  46. group_by_vat = fields.Boolean('VAT')
  47. group_by_parent_id = fields.Boolean('Parent Company')
  48. state = fields.Selection([
  49. ('option', 'Option'),
  50. ('selection', 'Selection'),
  51. ('finished', 'Finished')
  52. ], readonly=True, required=True, string='State', default='option')
  53. number_group = fields.Integer('Group of Contacts', readonly=True)
  54. current_line_id = fields.Many2one('base.partner.merge.line', string='Current Line')
  55. line_ids = fields.One2many('base.partner.merge.line', 'wizard_id', string='Lines')
  56. partner_ids = fields.Many2many('res.partner', string='Contacts', context={'active_test': False})
  57. dst_partner_id = fields.Many2one('res.partner', string='Destination Contact')
  58. exclude_contact = fields.Boolean('A user associated to the contact')
  59. exclude_journal_item = fields.Boolean('Journal Items associated to the contact')
  60. maximum_group = fields.Integer('Maximum of Group of Contacts')
  61. # ----------------------------------------
  62. # Update method. Core methods to merge steps
  63. # ----------------------------------------
  64. def _get_fk_on(self, table):
  65. """ return a list of many2one relation with the given table.
  66. :param table : the name of the sql table to return relations
  67. :returns a list of tuple 'table name', 'column name'.
  68. """
  69. query = """
  70. SELECT cl1.relname as table, att1.attname as column
  71. FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, pg_attribute as att1, pg_attribute as att2
  72. WHERE con.conrelid = cl1.oid
  73. AND con.confrelid = cl2.oid
  74. AND array_lower(con.conkey, 1) = 1
  75. AND con.conkey[1] = att1.attnum
  76. AND att1.attrelid = cl1.oid
  77. AND cl2.relname = %s
  78. AND att2.attname = 'id'
  79. AND array_lower(con.confkey, 1) = 1
  80. AND con.confkey[1] = att2.attnum
  81. AND att2.attrelid = cl2.oid
  82. AND con.contype = 'f'
  83. """
  84. self._cr.execute(query, (table,))
  85. return self._cr.fetchall()
  86. @api.model
  87. def _update_foreign_keys(self, src_partners, dst_partner):
  88. """ Update all foreign key from the src_partner to dst_partner. All many2one fields will be updated.
  89. :param src_partners : merge source res.partner recordset (does not include destination one)
  90. :param dst_partner : record of destination res.partner
  91. """
  92. _logger.debug('_update_foreign_keys for dst_partner: %s for src_partners: %s', dst_partner.id, str(src_partners.ids))
  93. # find the many2one relation to a partner
  94. Partner = self.env['res.partner']
  95. relations = self._get_fk_on('res_partner')
  96. # this guarantees cache consistency
  97. self.env.invalidate_all()
  98. for table, column in relations:
  99. if 'base_partner_merge_' in table: # ignore two tables
  100. continue
  101. # get list of columns of current table (exept the current fk column)
  102. query = "SELECT column_name FROM information_schema.columns WHERE table_name LIKE '%s'" % (table)
  103. self._cr.execute(query, ())
  104. columns = []
  105. for data in self._cr.fetchall():
  106. if data[0] != column:
  107. columns.append(data[0])
  108. # do the update for the current table/column in SQL
  109. query_dic = {
  110. 'table': table,
  111. 'column': column,
  112. 'value': columns[0],
  113. }
  114. if len(columns) <= 1:
  115. # unique key treated
  116. query = """
  117. UPDATE "%(table)s" as ___tu
  118. SET "%(column)s" = %%s
  119. WHERE
  120. "%(column)s" = %%s AND
  121. NOT EXISTS (
  122. SELECT 1
  123. FROM "%(table)s" as ___tw
  124. WHERE
  125. "%(column)s" = %%s AND
  126. ___tu.%(value)s = ___tw.%(value)s
  127. )""" % query_dic
  128. for partner in src_partners:
  129. self._cr.execute(query, (dst_partner.id, partner.id, dst_partner.id))
  130. else:
  131. try:
  132. with mute_logger('odoo.sql_db'), self._cr.savepoint():
  133. query = 'UPDATE "%(table)s" SET "%(column)s" = %%s WHERE "%(column)s" IN %%s' % query_dic
  134. self._cr.execute(query, (dst_partner.id, tuple(src_partners.ids),))
  135. # handle the recursivity with parent relation
  136. if column == Partner._parent_name and table == 'res_partner':
  137. query = """
  138. WITH RECURSIVE cycle(id, parent_id) AS (
  139. SELECT id, parent_id FROM res_partner
  140. UNION
  141. SELECT cycle.id, res_partner.parent_id
  142. FROM res_partner, cycle
  143. WHERE res_partner.id = cycle.parent_id AND
  144. cycle.id != cycle.parent_id
  145. )
  146. SELECT id FROM cycle WHERE id = parent_id AND id = %s
  147. """
  148. self._cr.execute(query, (dst_partner.id,))
  149. # NOTE JEM : shouldn't we fetch the data ?
  150. except psycopg2.Error:
  151. # updating fails, most likely due to a violated unique constraint
  152. # keeping record with nonexistent partner_id is useless, better delete it
  153. query = 'DELETE FROM "%(table)s" WHERE "%(column)s" IN %%s' % query_dic
  154. self._cr.execute(query, (tuple(src_partners.ids),))
  155. @api.model
  156. def _update_reference_fields(self, src_partners, dst_partner):
  157. """ Update all reference fields from the src_partner to dst_partner.
  158. :param src_partners : merge source res.partner recordset (does not include destination one)
  159. :param dst_partner : record of destination res.partner
  160. """
  161. _logger.debug('_update_reference_fields for dst_partner: %s for src_partners: %r', dst_partner.id, src_partners.ids)
  162. def update_records(model, src, field_model='model', field_id='res_id'):
  163. Model = self.env[model] if model in self.env else None
  164. if Model is None:
  165. return
  166. records = Model.sudo().search([(field_model, '=', 'res.partner'), (field_id, '=', src.id)])
  167. try:
  168. with mute_logger('odoo.sql_db'), self._cr.savepoint():
  169. records.sudo().write({field_id: dst_partner.id})
  170. records.env.flush_all()
  171. except psycopg2.Error:
  172. # updating fails, most likely due to a violated unique constraint
  173. # keeping record with nonexistent partner_id is useless, better delete it
  174. records.sudo().unlink()
  175. update_records = functools.partial(update_records)
  176. for partner in src_partners:
  177. update_records('calendar', src=partner, field_model='model_id.model')
  178. update_records('ir.attachment', src=partner, field_model='res_model')
  179. update_records('mail.followers', src=partner, field_model='res_model')
  180. update_records('mail.activity', src=partner, field_model='res_model')
  181. update_records('mail.message', src=partner)
  182. update_records('ir.model.data', src=partner)
  183. records = self.env['ir.model.fields'].sudo().search([('ttype', '=', 'reference')])
  184. for record in records:
  185. try:
  186. Model = self.env[record.model]
  187. field = Model._fields[record.name]
  188. except KeyError:
  189. # unknown model or field => skip
  190. continue
  191. if Model._abstract or field.compute is not None:
  192. continue
  193. for partner in src_partners:
  194. records_ref = Model.sudo().search([(record.name, '=', 'res.partner,%d' % partner.id)])
  195. values = {
  196. record.name: 'res.partner,%d' % dst_partner.id,
  197. }
  198. records_ref.sudo().write(values)
  199. self.env.flush_all()
  200. def _get_summable_fields(self):
  201. """ Returns the list of fields that should be summed when merging partners
  202. """
  203. return []
  204. @api.model
  205. def _update_values(self, src_partners, dst_partner):
  206. """ Update values of dst_partner with the ones from the src_partners.
  207. :param src_partners : recordset of source res.partner
  208. :param dst_partner : record of destination res.partner
  209. """
  210. _logger.debug('_update_values for dst_partner: %s for src_partners: %r', dst_partner.id, src_partners.ids)
  211. model_fields = dst_partner.fields_get().keys()
  212. summable_fields = self._get_summable_fields()
  213. def write_serializer(item):
  214. if isinstance(item, models.BaseModel):
  215. return item.id
  216. else:
  217. return item
  218. # get all fields that are not computed or x2many
  219. values = dict()
  220. values_by_company = defaultdict(dict) # {company: vals}
  221. for column in model_fields:
  222. field = dst_partner._fields[column]
  223. if field.type not in ('many2many', 'one2many') and field.compute is None:
  224. for item in itertools.chain(src_partners, [dst_partner]):
  225. if item[column]:
  226. if column in summable_fields and values.get(column):
  227. values[column] += write_serializer(item[column])
  228. else:
  229. values[column] = write_serializer(item[column])
  230. elif field.company_dependent and column in summable_fields:
  231. # sum the values of partners for each company; use sudo() to
  232. # compute the sum on all companies, including forbidden ones
  233. partners = (src_partners + dst_partner).sudo()
  234. for company in self.env['res.company'].sudo().search([]):
  235. values_by_company[company][column] = sum(
  236. partners.with_company(company).mapped(column)
  237. )
  238. # remove fields that can not be updated (id and parent_id)
  239. values.pop('id', None)
  240. parent_id = values.pop('parent_id', None)
  241. dst_partner.write(values)
  242. for company, vals in values_by_company.items():
  243. dst_partner.with_company(company).sudo().write(vals)
  244. # try to update the parent_id
  245. if parent_id and parent_id != dst_partner.id:
  246. try:
  247. dst_partner.write({'parent_id': parent_id})
  248. except ValidationError:
  249. _logger.info('Skip recursive partner hierarchies for parent_id %s of partner: %s', parent_id, dst_partner.id)
  250. def _merge(self, partner_ids, dst_partner=None, extra_checks=True):
  251. """ private implementation of merge partner
  252. :param partner_ids : ids of partner to merge
  253. :param dst_partner : record of destination res.partner
  254. :param extra_checks: pass False to bypass extra sanity check (e.g. email address)
  255. """
  256. # super-admin can be used to bypass extra checks
  257. if self.env.is_admin():
  258. extra_checks = False
  259. Partner = self.env['res.partner']
  260. partner_ids = Partner.browse(partner_ids).exists()
  261. if len(partner_ids) < 2:
  262. return
  263. if len(partner_ids) > 3:
  264. raise UserError(_("For safety reasons, you cannot merge more than 3 contacts together. You can re-open the wizard several times if needed."))
  265. # check if the list of partners to merge contains child/parent relation
  266. child_ids = self.env['res.partner']
  267. for partner_id in partner_ids:
  268. child_ids |= Partner.search([('id', 'child_of', [partner_id.id])]) - partner_id
  269. if partner_ids & child_ids:
  270. raise UserError(_("You cannot merge a contact with one of his parent."))
  271. if extra_checks and len(set(partner.email for partner in partner_ids)) > 1:
  272. raise UserError(_("All contacts must have the same email. Only the Administrator can merge contacts with different emails."))
  273. # remove dst_partner from partners to merge
  274. if dst_partner and dst_partner in partner_ids:
  275. src_partners = partner_ids - dst_partner
  276. else:
  277. ordered_partners = self._get_ordered_partner(partner_ids.ids)
  278. dst_partner = ordered_partners[-1]
  279. src_partners = ordered_partners[:-1]
  280. _logger.info("dst_partner: %s", dst_partner.id)
  281. # Make the company of all related users consistent with destination partner company
  282. if dst_partner.company_id:
  283. partner_ids.mapped('user_ids').sudo().write({
  284. 'company_ids': [Command.link(dst_partner.company_id.id)],
  285. 'company_id': dst_partner.company_id.id
  286. })
  287. # call sub methods to do the merge
  288. self._update_foreign_keys(src_partners, dst_partner)
  289. self._update_reference_fields(src_partners, dst_partner)
  290. self._update_values(src_partners, dst_partner)
  291. self._log_merge_operation(src_partners, dst_partner)
  292. # delete source partner, since they are merged
  293. src_partners.unlink()
  294. def _log_merge_operation(self, src_partners, dst_partner):
  295. _logger.info('(uid = %s) merged the partners %r with %s', self._uid, src_partners.ids, dst_partner.id)
  296. # ----------------------------------------
  297. # Helpers
  298. # ----------------------------------------
  299. @api.model
  300. def _generate_query(self, fields, maximum_group=100):
  301. """ Build the SQL query on res.partner table to group them according to given criteria
  302. :param fields : list of column names to group by the partners
  303. :param maximum_group : limit of the query
  304. """
  305. # make the list of column to group by in sql query
  306. sql_fields = []
  307. for field in fields:
  308. if field in ['email', 'name']:
  309. sql_fields.append('lower(%s)' % field)
  310. elif field in ['vat']:
  311. sql_fields.append("replace(%s, ' ', '')" % field)
  312. else:
  313. sql_fields.append(field)
  314. group_fields = ', '.join(sql_fields)
  315. # where clause : for given group by columns, only keep the 'not null' record
  316. filters = []
  317. for field in fields:
  318. if field in ['email', 'name', 'vat']:
  319. filters.append((field, 'IS NOT', 'NULL'))
  320. criteria = ' AND '.join('%s %s %s' % (field, operator, value) for field, operator, value in filters)
  321. # build the query
  322. text = [
  323. "SELECT min(id), array_agg(id)",
  324. "FROM res_partner",
  325. ]
  326. if criteria:
  327. text.append('WHERE %s' % criteria)
  328. text.extend([
  329. "GROUP BY %s" % group_fields,
  330. "HAVING COUNT(*) >= 2",
  331. "ORDER BY min(id)",
  332. ])
  333. if maximum_group:
  334. text.append("LIMIT %s" % maximum_group,)
  335. return ' '.join(text)
  336. @api.model
  337. def _compute_selected_groupby(self):
  338. """ Returns the list of field names the partner can be grouped (as merge
  339. criteria) according to the option checked on the wizard
  340. """
  341. groups = []
  342. group_by_prefix = 'group_by_'
  343. for field_name in self._fields:
  344. if field_name.startswith(group_by_prefix):
  345. if getattr(self, field_name, False):
  346. groups.append(field_name[len(group_by_prefix):])
  347. if not groups:
  348. raise UserError(_("You have to specify a filter for your selection."))
  349. return groups
  350. @api.model
  351. def _partner_use_in(self, aggr_ids, models):
  352. """ Check if there is no occurence of this group of partner in the selected model
  353. :param aggr_ids : stringified list of partner ids separated with a comma (sql array_agg)
  354. :param models : dict mapping a model name with its foreign key with res_partner table
  355. """
  356. return any(
  357. self.env[model].search_count([(field, 'in', aggr_ids)])
  358. for model, field in models.items()
  359. )
  360. @api.model
  361. def _get_ordered_partner(self, partner_ids):
  362. """ Helper : returns a `res.partner` recordset ordered by create_date/active fields
  363. :param partner_ids : list of partner ids to sort
  364. """
  365. return self.env['res.partner'].browse(partner_ids).sorted(
  366. key=lambda p: (not p.active, (p.create_date or datetime.datetime(1970, 1, 1))),
  367. reverse=True,
  368. )
  369. def _compute_models(self):
  370. """ Compute the different models needed by the system if you want to exclude some partners. """
  371. model_mapping = {}
  372. if self.exclude_contact:
  373. model_mapping['res.users'] = 'partner_id'
  374. if 'account.move.line' in self.env and self.exclude_journal_item:
  375. model_mapping['account.move.line'] = 'partner_id'
  376. return model_mapping
  377. # ----------------------------------------
  378. # Actions
  379. # ----------------------------------------
  380. def action_skip(self):
  381. """ Skip this wizard line. Don't compute any thing, and simply redirect to the new step."""
  382. if self.current_line_id:
  383. self.current_line_id.unlink()
  384. return self._action_next_screen()
  385. def _action_next_screen(self):
  386. """ return the action of the next screen ; this means the wizard is set to treat the
  387. next wizard line. Each line is a subset of partner that can be merged together.
  388. If no line left, the end screen will be displayed (but an action is still returned).
  389. """
  390. self.env.invalidate_all() # FIXME: is this still necessary?
  391. values = {}
  392. if self.line_ids:
  393. # in this case, we try to find the next record.
  394. current_line = self.line_ids[0]
  395. current_partner_ids = literal_eval(current_line.aggr_ids)
  396. values.update({
  397. 'current_line_id': current_line.id,
  398. 'partner_ids': [Command.set(current_partner_ids)],
  399. 'dst_partner_id': self._get_ordered_partner(current_partner_ids)[-1].id,
  400. 'state': 'selection',
  401. })
  402. else:
  403. values.update({
  404. 'current_line_id': False,
  405. 'partner_ids': [],
  406. 'state': 'finished',
  407. })
  408. self.write(values)
  409. return {
  410. 'type': 'ir.actions.act_window',
  411. 'res_model': self._name,
  412. 'res_id': self.id,
  413. 'view_mode': 'form',
  414. 'target': 'new',
  415. }
  416. def _process_query(self, query):
  417. """ Execute the select request and write the result in this wizard
  418. :param query : the SQL query used to fill the wizard line
  419. """
  420. self.ensure_one()
  421. model_mapping = self._compute_models()
  422. # group partner query
  423. self._cr.execute(query) # pylint: disable=sql-injection
  424. counter = 0
  425. for min_id, aggr_ids in self._cr.fetchall():
  426. # To ensure that the used partners are accessible by the user
  427. partners = self.env['res.partner'].search([('id', 'in', aggr_ids)])
  428. if len(partners) < 2:
  429. continue
  430. # exclude partner according to options
  431. if model_mapping and self._partner_use_in(partners.ids, model_mapping):
  432. continue
  433. self.env['base.partner.merge.line'].create({
  434. 'wizard_id': self.id,
  435. 'min_id': min_id,
  436. 'aggr_ids': partners.ids,
  437. })
  438. counter += 1
  439. self.write({
  440. 'state': 'selection',
  441. 'number_group': counter,
  442. })
  443. _logger.info("counter: %s", counter)
  444. def action_start_manual_process(self):
  445. """ Start the process 'Merge with Manual Check'. Fill the wizard according to the group_by and exclude
  446. options, and redirect to the first step (treatment of first wizard line). After, for each subset of
  447. partner to merge, the wizard will be actualized.
  448. - Compute the selected groups (with duplication)
  449. - If the user has selected the 'exclude_xxx' fields, avoid the partners
  450. """
  451. self.ensure_one()
  452. groups = self._compute_selected_groupby()
  453. query = self._generate_query(groups, self.maximum_group)
  454. self._process_query(query)
  455. return self._action_next_screen()
  456. def action_start_automatic_process(self):
  457. """ Start the process 'Merge Automatically'. This will fill the wizard with the same mechanism as 'Merge
  458. with Manual Check', but instead of refreshing wizard with the current line, it will automatically process
  459. all lines by merging partner grouped according to the checked options.
  460. """
  461. self.ensure_one()
  462. self.action_start_manual_process() # here we don't redirect to the next screen, since it is automatic process
  463. self.env.invalidate_all() # FIXME: is this still necessary?
  464. for line in self.line_ids:
  465. partner_ids = literal_eval(line.aggr_ids)
  466. self._merge(partner_ids)
  467. line.unlink()
  468. self._cr.commit() # TODO JEM : explain why
  469. self.write({'state': 'finished'})
  470. return {
  471. 'type': 'ir.actions.act_window',
  472. 'res_model': self._name,
  473. 'res_id': self.id,
  474. 'view_mode': 'form',
  475. 'target': 'new',
  476. }
  477. def parent_migration_process_cb(self):
  478. self.ensure_one()
  479. query = """
  480. SELECT
  481. min(p1.id),
  482. array_agg(DISTINCT p1.id)
  483. FROM
  484. res_partner as p1
  485. INNER join
  486. res_partner as p2
  487. ON
  488. p1.email = p2.email AND
  489. p1.name = p2.name AND
  490. (p1.parent_id = p2.id OR p1.id = p2.parent_id)
  491. WHERE
  492. p2.id IS NOT NULL
  493. GROUP BY
  494. p1.email,
  495. p1.name,
  496. CASE WHEN p1.parent_id = p2.id THEN p2.id
  497. ELSE p1.id
  498. END
  499. HAVING COUNT(*) >= 2
  500. ORDER BY
  501. min(p1.id)
  502. """
  503. self._process_query(query)
  504. for line in self.line_ids:
  505. partner_ids = literal_eval(line.aggr_ids)
  506. self._merge(partner_ids)
  507. line.unlink()
  508. self._cr.commit()
  509. self.write({'state': 'finished'})
  510. self._cr.execute("""
  511. UPDATE
  512. res_partner
  513. SET
  514. is_company = NULL,
  515. parent_id = NULL
  516. WHERE
  517. parent_id = id
  518. """)
  519. return {
  520. 'type': 'ir.actions.act_window',
  521. 'res_model': self._name,
  522. 'res_id': self.id,
  523. 'view_mode': 'form',
  524. 'target': 'new',
  525. }
  526. def action_update_all_process(self):
  527. self.ensure_one()
  528. self.parent_migration_process_cb()
  529. # NOTE JEM : seems louche to create a new wizard instead of reuse the current one with updated options.
  530. # since it is like this from the initial commit of this wizard, I don't change it. yet ...
  531. wizard = self.create({'group_by_vat': True, 'group_by_email': True, 'group_by_name': True})
  532. wizard.action_start_automatic_process()
  533. # NOTE JEM : no idea if this query is usefull
  534. self._cr.execute("""
  535. UPDATE
  536. res_partner
  537. SET
  538. is_company = NULL
  539. WHERE
  540. parent_id IS NOT NULL AND
  541. is_company IS NOT NULL
  542. """)
  543. return self._action_next_screen()
  544. def action_merge(self):
  545. """ Merge Contact button. Merge the selected partners, and redirect to
  546. the end screen (since there is no other wizard line to process.
  547. """
  548. if not self.partner_ids:
  549. self.write({'state': 'finished'})
  550. return {
  551. 'type': 'ir.actions.act_window',
  552. 'res_model': self._name,
  553. 'res_id': self.id,
  554. 'view_mode': 'form',
  555. 'target': 'new',
  556. }
  557. self._merge(self.partner_ids.ids, self.dst_partner_id)
  558. if self.current_line_id:
  559. self.current_line_id.unlink()
  560. return self._action_next_screen()