You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

857 lines
32 KiB

  1. #!/usr/bin/env python
  2. from __future__ import absolute_import
  3. from email.utils import parseaddr
  4. import functools
  5. import htmlentitydefs
  6. import itertools
  7. import logging
  8. import operator
  9. import re
  10. from ast import literal_eval
  11. from openerp.tools import mute_logger
  12. # Validation Library https://pypi.python.org/pypi/validate_email/1.1
  13. from .validate_email import validate_email
  14. import openerp
  15. from openerp.osv import osv, orm
  16. from openerp.osv import fields
  17. from openerp.osv.orm import browse_record
  18. from openerp.tools.translate import _
  19. pattern = re.compile("&(\w+?);")
  20. _logger = logging.getLogger('base.partner.merge')
  21. # http://www.php2python.com/wiki/function.html-entity-decode/
  22. def html_entity_decode_char(m, defs=htmlentitydefs.entitydefs):
  23. try:
  24. return defs[m.group(1)]
  25. except KeyError:
  26. return m.group(0)
  27. def html_entity_decode(string):
  28. return pattern.sub(html_entity_decode_char, string)
  29. def sanitize_email(email):
  30. assert isinstance(email, basestring) and email
  31. result = re.subn(r';|/|:', ',',
  32. html_entity_decode(email or ''))[0].split(',')
  33. emails = [parseaddr(email)[1]
  34. for item in result
  35. for email in item.split()]
  36. return [email.lower()
  37. for email in emails
  38. if validate_email(email)]
  39. def is_integer_list(ids):
  40. return all(isinstance(i, (int, long)) for i in ids)
  41. class MergePartnerLine(osv.TransientModel):
  42. _name = 'base.partner.merge.line'
  43. _columns = {
  44. 'wizard_id': fields.many2one('base.partner.merge.automatic.wizard',
  45. 'Wizard'),
  46. 'min_id': fields.integer('MinID'),
  47. 'aggr_ids': fields.char('Ids', required=True),
  48. }
  49. _order = 'min_id asc'
  50. class MergePartnerAutomatic(osv.TransientModel):
  51. """
  52. The idea behind this wizard is to create a list of potential partners
  53. to merge. We use two objects, the first one is the wizard for
  54. the end-user.
  55. And the second will contain the partner list to merge.
  56. """
  57. _name = 'base.partner.merge.automatic.wizard'
  58. _columns = {
  59. # Group by
  60. 'group_by_email': fields.boolean('Email'),
  61. 'group_by_name': fields.boolean('Name'),
  62. 'group_by_is_company': fields.boolean('Is Company'),
  63. 'group_by_vat': fields.boolean('VAT'),
  64. 'group_by_parent_id': fields.boolean('Parent Company'),
  65. 'state': fields.selection([('option', 'Option'),
  66. ('selection', 'Selection'),
  67. ('finished', 'Finished')],
  68. 'State',
  69. readonly=True,
  70. required=True),
  71. 'number_group': fields.integer("Group of Contacts", readonly=True),
  72. 'current_line_id': fields.many2one('base.partner.merge.line',
  73. 'Current Line'),
  74. 'line_ids': fields.one2many('base.partner.merge.line',
  75. 'wizard_id', 'Lines'),
  76. 'partner_ids': fields.many2many('res.partner', string='Contacts'),
  77. 'dst_partner_id': fields.many2one('res.partner',
  78. string='Destination Contact'),
  79. 'exclude_contact': fields.boolean('A user associated to the contact'),
  80. 'exclude_journal_item': fields.boolean('Journal Items associated to '
  81. 'the contact'),
  82. 'maximum_group': fields.integer("Maximum of Group of Contacts"),
  83. }
  84. def default_get(self, cr, uid, fields, context=None):
  85. if context is None:
  86. context = {}
  87. res = super(MergePartnerAutomatic, self).default_get(
  88. cr, uid, fields, context)
  89. if context.get('active_model') == 'res.partner' and \
  90. context.get('active_ids'):
  91. partner_ids = context['active_ids']
  92. res['state'] = 'selection'
  93. res['partner_ids'] = partner_ids
  94. res['dst_partner_id'] = self._get_ordered_partner(
  95. cr, uid, partner_ids, context=context)[-1].id
  96. return res
  97. _defaults = {
  98. 'state': 'option'
  99. }
  100. def get_fk_on(self, cr, table):
  101. q = """ SELECT cl1.relname as table,
  102. att1.attname as column
  103. FROM pg_constraint as con, pg_class as cl1, pg_class as cl2,
  104. pg_attribute as att1, pg_attribute as att2
  105. WHERE con.conrelid = cl1.oid
  106. AND con.confrelid = cl2.oid
  107. AND array_lower(con.conkey, 1) = 1
  108. AND con.conkey[1] = att1.attnum
  109. AND att1.attrelid = cl1.oid
  110. AND cl2.relname = %s
  111. AND att2.attname = 'id'
  112. AND array_lower(con.confkey, 1) = 1
  113. AND con.confkey[1] = att2.attnum
  114. AND att2.attrelid = cl2.oid
  115. AND con.contype = 'f'
  116. """
  117. return cr.execute(q, (table,))
  118. def _update_foreign_keys(self, cr, uid, src_partners, dst_partner,
  119. context=None):
  120. _logger.debug(
  121. '_update_foreign_keys for dst_partner: %s for src_partners: %r',
  122. dst_partner.id, list(map(operator.attrgetter('id'), src_partners)))
  123. # find the many2one relation to a partner
  124. proxy = self.pool.get('res.partner')
  125. self.get_fk_on(cr, 'res_partner')
  126. # ignore two tables
  127. for table, column in cr.fetchall():
  128. if 'base_partner_merge_' in table:
  129. continue
  130. partner_ids = tuple(map(int, src_partners))
  131. query = """SELECT column_name FROM information_schema.columns
  132. WHERE table_name LIKE '%s'""" % (
  133. table)
  134. cr.execute(query, ())
  135. columns = []
  136. for data in cr.fetchall():
  137. if data[0] != column:
  138. columns.append(data[0])
  139. query_dic = {
  140. 'table': table,
  141. 'column': column,
  142. 'value': columns[0],
  143. }
  144. if len(columns) <= 1:
  145. # unique key treated
  146. query = """
  147. UPDATE "%(table)s" as ___tu
  148. SET %(column)s = %%s
  149. WHERE
  150. %(column)s = %%s AND
  151. NOT EXISTS (
  152. SELECT 1
  153. FROM "%(table)s" as ___tw
  154. WHERE
  155. %(column)s = %%s AND
  156. ___tu.%(value)s = ___tw.%(value)s
  157. )""" % query_dic
  158. for partner_id in partner_ids:
  159. cr.execute(query, (
  160. dst_partner.id, partner_id, dst_partner.id))
  161. else:
  162. cr.execute("SAVEPOINT recursive_partner_savepoint")
  163. try:
  164. query = '''UPDATE "%(table)s" SET %(column)s = %%s
  165. WHERE %(column)s IN %%s''' % query_dic
  166. cr.execute(query, (dst_partner.id, partner_ids,))
  167. if column == proxy._parent_name and table == 'res_partner':
  168. query = """
  169. WITH RECURSIVE cycle(id, parent_id) AS (
  170. SELECT id, parent_id FROM res_partner
  171. UNION
  172. SELECT cycle.id, res_partner.parent_id
  173. FROM res_partner, cycle
  174. WHERE res_partner.id = cycle.parent_id AND
  175. cycle.id != cycle.parent_id
  176. )
  177. SELECT id FROM cycle
  178. WHERE id = parent_id AND id = %s
  179. """
  180. cr.execute(query, (dst_partner.id,))
  181. if cr.fetchall():
  182. cr.execute(
  183. "ROLLBACK TO SAVEPOINT "
  184. "recursive_partner_savepoint")
  185. finally:
  186. cr.execute("RELEASE SAVEPOINT recursive_partner_savepoint")
  187. def _update_reference_fields(self, cr, uid, src_partners,
  188. dst_partner, context=None):
  189. _logger.debug('''_update_reference_fields for dst_partner: %s
  190. for src_partners: %r''', dst_partner.id, list(
  191. map(operator.attrgetter('id'), src_partners)))
  192. def update_records(model, src, field_model='model',
  193. field_id='res_id', context=None):
  194. proxy = self.pool.get(model)
  195. if proxy is None:
  196. return
  197. domain = [(field_model, '=', 'res.partner'), (
  198. field_id, '=', src.id)]
  199. ids = proxy.search(
  200. cr, openerp.SUPERUSER_ID, domain, context=context)
  201. return proxy.write(cr, openerp.SUPERUSER_ID, ids,
  202. {field_id: dst_partner.id}, context=context)
  203. update_records = functools.partial(update_records, context=context)
  204. for partner in src_partners:
  205. update_records(
  206. 'base.calendar', src=partner, field_model='model_id.model')
  207. update_records(
  208. 'ir.attachment', src=partner, field_model='res_model')
  209. update_records(
  210. 'mail.followers', src=partner, field_model='res_model')
  211. update_records('mail.message', src=partner)
  212. update_records('marketing.campaign.workitem',
  213. src=partner, field_model='object_id.model')
  214. update_records('ir.model.data', src=partner)
  215. proxy = self.pool['ir.model.fields']
  216. domain = [('ttype', '=', 'reference')]
  217. record_ids = proxy.search(
  218. cr, openerp.SUPERUSER_ID, domain, context=context)
  219. for record in proxy.browse(cr, openerp.SUPERUSER_ID, record_ids,
  220. context=context):
  221. proxy_model = self.pool[record.model]
  222. field_type = proxy_model._columns.get(record.name).__class__._type
  223. if field_type == 'function':
  224. continue
  225. for partner in src_partners:
  226. domain = [
  227. (record.name, '=', 'res.partner,%d' % partner.id)
  228. ]
  229. model_ids = proxy_model.search(
  230. cr, openerp.SUPERUSER_ID, domain, context=context)
  231. values = {
  232. record.name: 'res.partner,%d' % dst_partner.id,
  233. }
  234. proxy_model.write(
  235. cr, openerp.SUPERUSER_ID, model_ids, values,
  236. context=context)
  237. def _update_values(self, cr, uid, src_partners, dst_partner, context=None):
  238. _logger.debug(
  239. '_update_values for dst_partner: %s for src_partners: %r',
  240. dst_partner.id, list(map(operator.attrgetter('id'), src_partners)))
  241. columns = dst_partner._columns
  242. def write_serializer(column, item):
  243. if isinstance(item, browse_record):
  244. return item.id
  245. else:
  246. return item
  247. values = dict()
  248. for column, field in columns.iteritems():
  249. if field._type not in ('many2many', 'one2many') and not \
  250. isinstance(field, fields.function):
  251. for item in itertools.chain(src_partners, [dst_partner]):
  252. if item[column]:
  253. values[column] = write_serializer(column, item[column])
  254. values.pop('id', None)
  255. parent_id = values.pop('parent_id', None)
  256. dst_partner.write(values)
  257. if parent_id and parent_id != dst_partner.id:
  258. try:
  259. dst_partner.write({'parent_id': parent_id})
  260. except (osv.except_osv, orm.except_orm):
  261. _logger.info(
  262. '''Skip recursive partner hierarchies
  263. for parent_id %s of partner: %s''', parent_id,
  264. dst_partner.id)
  265. @mute_logger('openerp.osv.expression', 'openerp.osv.orm')
  266. def _merge(self, cr, uid, partner_ids, dst_partner=None, remove=None,context=None):
  267. proxy = self.pool.get('res.partner')
  268. partner_ids = proxy.exists(cr, uid, list(partner_ids), context=context)
  269. if len(partner_ids) < 2:
  270. return
  271. if len(partner_ids) > 10:
  272. raise osv.except_osv(_('Error'), _(
  273. """For safety reasons, you cannot merge more than 10 contacts
  274. together. You can re-open the wizard several
  275. times if needed."""))
  276. if openerp.SUPERUSER_ID != uid and \
  277. len(set(partner.email for partner in
  278. proxy.browse(cr, uid,
  279. partner_ids,
  280. context))) > 1:
  281. raise osv.except_osv(_('Error'), _(
  282. """All contacts must have the same email. Only the
  283. Administrator can merge contacts with different
  284. emails."""))
  285. if dst_partner and dst_partner.id in partner_ids:
  286. src_partners = proxy.browse(cr, uid, [
  287. id for id in partner_ids
  288. if id != dst_partner.id],
  289. context=context)
  290. else:
  291. ordered_partners = self._get_ordered_partner(
  292. cr, uid, partner_ids, context)
  293. dst_partner = ordered_partners[-1]
  294. src_partners = ordered_partners[:-1]
  295. _logger.info("dst_partner: %s", dst_partner.id)
  296. if openerp.SUPERUSER_ID != uid and \
  297. self._model_is_installed(cr, uid, 'account.move.line',
  298. context=context) and \
  299. self.pool.get('account.move.line').\
  300. search(cr, openerp.SUPERUSER_ID,
  301. [('partner_id', 'in', [partner.id for partner in
  302. src_partners])],
  303. context=context):
  304. raise osv.except_osv(_('Error'), _(
  305. """Only the destination contact may be linked to existing
  306. Journal Items. Please ask the Administrator if you need
  307. to merge several contacts linked to existing Journal
  308. Items."""))
  309. call_it = lambda function: function(cr, uid, src_partners, dst_partner,
  310. context=context)
  311. call_it(self._update_foreign_keys)
  312. call_it(self._update_reference_fields)
  313. call_it(self._update_values)
  314. _logger.info('(uid = %s) merged the partners %r with %s', uid, list(
  315. map(operator.attrgetter('id'), src_partners)), dst_partner.id)
  316. dst_partner.message_post(body='%s %s' %
  317. (_("Merged with the following partners:"),
  318. ", ".join('%s<%s>(ID %s)' %
  319. (p.name, p.email or 'n/a', p.id) for p in
  320. src_partners)))
  321. if remove == False:
  322. for partner in src_partners:
  323. partner.write({'active' : False})
  324. else:
  325. for partner in src_partners:
  326. partner.unlink()
  327. def clean_emails(self, cr, uid, context=None):
  328. """
  329. Clean the email address of the partner, if there is an email field
  330. with a mimum of two addresses, the system will create a new partner,
  331. with the information of the previous one and will copy the new cleaned
  332. email into the email field.
  333. """
  334. if context is None:
  335. context = {}
  336. proxy_model = self.pool['ir.model.fields']
  337. field_ids = proxy_model.search(cr, uid, [('model', '=', 'res.partner'),
  338. ('ttype', 'like', '%2many')],
  339. context=context)
  340. fields = proxy_model.read(cr, uid, field_ids, context=context)
  341. reset_fields = dict((field['name'], []) for field in fields)
  342. proxy_partner = self.pool['res.partner']
  343. context['active_test'] = False
  344. ids = proxy_partner.search(cr, uid, [], context=context)
  345. fields = ['name', 'var' 'partner_id' 'is_company', 'email']
  346. partners = proxy_partner.read(cr, uid, ids, fields, context=context)
  347. partners.sort(key=operator.itemgetter('id'))
  348. partners_len = len(partners)
  349. _logger.info('partner_len: %r', partners_len)
  350. for idx, partner in enumerate(partners):
  351. if not partner['email']:
  352. continue
  353. percent = (idx / float(partners_len)) * 100.0
  354. _logger.info('idx: %r', idx)
  355. _logger.info('percent: %r', percent)
  356. try:
  357. emails = sanitize_email(partner['email'])
  358. head, tail = emails[:1], emails[1:]
  359. email = head[0] if head else False
  360. proxy_partner.write(cr, uid, [partner['id']],
  361. {'email': email}, context=context)
  362. for email in tail:
  363. values = dict(reset_fields, email=email)
  364. proxy_partner.copy(cr, uid, partner['id'], values,
  365. context=context)
  366. except Exception:
  367. _logger.exception(
  368. "There is a problem with this partner: %r", partner)
  369. raise
  370. return True
  371. def close_cb(self, cr, uid, ids, context=None):
  372. return {'type': 'ir.actions.act_window_close'}
  373. def _generate_query(self, fields, maximum_group=100):
  374. group_fields = ', '.join(fields)
  375. filters = []
  376. for field in fields:
  377. if field in ['email', 'name']:
  378. filters.append((field, 'IS NOT', 'NULL'))
  379. criteria = ' AND '.join('%s %s %s' % (field, operator, value)
  380. for field, operator, value in filters)
  381. text = [
  382. "SELECT min(id), array_agg(id)",
  383. "FROM res_partner",
  384. ]
  385. if criteria:
  386. text.append('WHERE %s' % criteria)
  387. text.extend([
  388. "GROUP BY %s" % group_fields,
  389. "HAVING COUNT(*) >= 2",
  390. "ORDER BY min(id)",
  391. ])
  392. if maximum_group:
  393. text.extend([
  394. "LIMIT %s" % maximum_group,
  395. ])
  396. return ' '.join(text)
  397. def _compute_selected_groupby(self, this):
  398. group_by_str = 'group_by_'
  399. group_by_len = len(group_by_str)
  400. fields = [
  401. key[group_by_len:]
  402. for key in self._columns.keys()
  403. if key.startswith(group_by_str)
  404. ]
  405. groups = [
  406. field
  407. for field in fields
  408. if getattr(this, '%s%s' % (group_by_str, field), False)
  409. ]
  410. if not groups:
  411. raise osv.except_osv(_('Error'),
  412. _("""You have to specify a filter for your
  413. selection"""))
  414. return groups
  415. def next_cb(self, cr, uid, ids, context=None):
  416. """
  417. Don't compute any thing
  418. """
  419. context = dict(context or {}, active_test=False)
  420. this = self.browse(cr, uid, ids[0], context=context)
  421. if this.current_line_id:
  422. this.current_line_id.unlink()
  423. return self._next_screen(cr, uid, this, context)
  424. def _get_ordered_partner(self, cr, uid, partner_ids, context=None):
  425. partners = self.pool.get('res.partner').browse(
  426. cr, uid, list(partner_ids), context=context)
  427. ordered_partners = sorted(sorted(partners,
  428. key=operator.attrgetter(
  429. 'create_date'), reverse=True),
  430. key=operator.attrgetter('active'),
  431. reverse=True)
  432. return ordered_partners
  433. def _next_screen(self, cr, uid, this, context=None):
  434. this.refresh()
  435. values = {}
  436. if this.line_ids:
  437. # in this case, we try to find the next record.
  438. current_line = this.line_ids[0]
  439. current_partner_ids = literal_eval(current_line.aggr_ids)
  440. values.update({
  441. 'current_line_id': current_line.id,
  442. 'partner_ids': [(6, 0, current_partner_ids)],
  443. 'dst_partner_id': self.
  444. _get_ordered_partner(cr, uid,
  445. current_partner_ids,
  446. context)[-1].id,
  447. 'state': 'selection',
  448. })
  449. else:
  450. values.update({
  451. 'current_line_id': False,
  452. 'partner_ids': [],
  453. 'state': 'finished',
  454. })
  455. this.write(values)
  456. return {
  457. 'type': 'ir.actions.act_window',
  458. 'res_model': this._name,
  459. 'res_id': this.id,
  460. 'view_mode': 'form',
  461. 'target': 'new',
  462. }
  463. def _model_is_installed(self, cr, uid, model, context=None):
  464. proxy = self.pool.get('ir.model')
  465. domain = [('model', '=', model)]
  466. return proxy.search_count(cr, uid, domain, context=context) > 0
  467. def _partner_use_in(self, cr, uid, aggr_ids, models, context=None):
  468. """
  469. Check if there is no occurence of this group of partner in the selected
  470. model
  471. """
  472. for model, field in models.iteritems():
  473. proxy = self.pool.get(model)
  474. domain = [(field, 'in', aggr_ids)]
  475. if proxy.search_count(cr, uid, domain, context=context):
  476. return True
  477. return False
  478. def compute_models(self, cr, uid, ids, context=None):
  479. """
  480. Compute the different models needed by the system if you want to
  481. exclude some partners.
  482. """
  483. assert is_integer_list(ids)
  484. this = self.browse(cr, uid, ids[0], context=context)
  485. models = {}
  486. if this.exclude_contact:
  487. models['res.users'] = 'partner_id'
  488. if self._model_is_installed(cr, uid, 'account.move.line',
  489. context=context) and \
  490. this.exclude_journal_item:
  491. models['account.move.line'] = 'partner_id'
  492. return models
  493. def _process_query(self, cr, uid, ids, query, context=None):
  494. """
  495. Execute the select request and write the result in this wizard
  496. """
  497. proxy = self.pool.get('base.partner.merge.line')
  498. this = self.browse(cr, uid, ids[0], context=context)
  499. models = self.compute_models(cr, uid, ids, context=context)
  500. cr.execute(query)
  501. counter = 0
  502. for min_id, aggr_ids in cr.fetchall():
  503. if models and self._partner_use_in(cr, uid, aggr_ids,
  504. models, context=context):
  505. continue
  506. values = {
  507. 'wizard_id': this.id,
  508. 'min_id': min_id,
  509. 'aggr_ids': aggr_ids,
  510. }
  511. proxy.create(cr, uid, values, context=context)
  512. counter += 1
  513. values = {
  514. 'state': 'selection',
  515. 'number_group': counter,
  516. }
  517. this.write(values)
  518. _logger.info("counter: %s", counter)
  519. def start_process_cb(self, cr, uid, ids, context=None):
  520. """
  521. Start the process.
  522. * Compute the selected groups (with duplication)
  523. * If the user has selected the 'exclude_XXX' fields, avoid the
  524. partners.
  525. """
  526. assert is_integer_list(ids)
  527. context = dict(context or {}, active_test=False)
  528. this = self.browse(cr, uid, ids[0], context=context)
  529. groups = self._compute_selected_groupby(this)
  530. query = self._generate_query(groups, this.maximum_group)
  531. self._process_query(cr, uid, ids, query, context=context)
  532. return self._next_screen(cr, uid, this, context)
  533. def automatic_process_cb(self, cr, uid, ids, context=None):
  534. assert is_integer_list(ids)
  535. this = self.browse(cr, uid, ids[0], context=context)
  536. this.start_process_cb()
  537. this.refresh()
  538. for line in this.line_ids:
  539. partner_ids = literal_eval(line.aggr_ids)
  540. self._merge(cr, uid, partner_ids, context=context)
  541. line.unlink()
  542. cr.commit()
  543. this.write({'state': 'finished'})
  544. return {
  545. 'type': 'ir.actions.act_window',
  546. 'res_model': this._name,
  547. 'res_id': this.id,
  548. 'view_mode': 'form',
  549. 'target': 'new',
  550. }
  551. def parent_migration_process_cb(self, cr, uid, ids, context=None):
  552. assert is_integer_list(ids)
  553. context = dict(context or {}, active_test=False)
  554. this = self.browse(cr, uid, ids[0], context=context)
  555. query = """
  556. SELECT
  557. min(p1.id),
  558. array_agg(DISTINCT p1.id)
  559. FROM
  560. res_partner as p1
  561. INNER join
  562. res_partner as p2
  563. ON
  564. p1.email = p2.email AND
  565. p1.name = p2.name AND
  566. (p1.parent_id = p2.id OR p1.id = p2.parent_id)
  567. WHERE
  568. p2.id IS NOT NULL
  569. GROUP BY
  570. p1.email,
  571. p1.name,
  572. CASE WHEN p1.parent_id = p2.id THEN p2.id
  573. ELSE p1.id
  574. END
  575. HAVING COUNT(*) >= 2
  576. ORDER BY
  577. min(p1.id)
  578. """
  579. self._process_query(cr, uid, ids, query, context=context)
  580. for line in this.line_ids:
  581. partner_ids = literal_eval(line.aggr_ids)
  582. self._merge(cr, uid, partner_ids, context=context)
  583. line.unlink()
  584. cr.commit()
  585. this.write({'state': 'finished'})
  586. cr.execute("""
  587. UPDATE
  588. res_partner
  589. SET
  590. is_company = NULL,
  591. parent_id = NULL
  592. WHERE
  593. parent_id = id
  594. """)
  595. return {
  596. 'type': 'ir.actions.act_window',
  597. 'res_model': this._name,
  598. 'res_id': this.id,
  599. 'view_mode': 'form',
  600. 'target': 'new',
  601. }
  602. def update_all_process_cb(self, cr, uid, ids, context=None):
  603. assert is_integer_list(ids)
  604. # WITH RECURSIVE cycle(id, parent_id) AS (
  605. # SELECT id, parent_id FROM res_partner
  606. # UNION
  607. # SELECT cycle.id, res_partner.parent_id
  608. # FROM res_partner, cycle
  609. # WHERE res_partner.id = cycle.parent_id AND
  610. # cycle.id != cycle.parent_id
  611. # )
  612. # UPDATE res_partner
  613. # SET parent_id = NULL
  614. # WHERE id in (SELECT id FROM cycle WHERE id = parent_id);
  615. this = self.browse(cr, uid, ids[0], context=context)
  616. self.parent_migration_process_cb(cr, uid, ids, context=None)
  617. list_merge = [
  618. {'group_by_vat': True, 'group_by_email':
  619. True, 'group_by_name': True},
  620. # {'group_by_name': True, 'group_by_is_company': True, 'group_by_parent_id': True},
  621. # {'group_by_email': True, 'group_by_is_company': True, 'group_by_parent_id': True},
  622. # {'group_by_name': True, 'group_by_vat': True, 'group_by_is_company': True, 'exclude_journal_item': True},
  623. # {'group_by_email': True, 'group_by_vat': True, 'group_by_is_company': True, 'exclude_journal_item': True},
  624. # {'group_by_email': True, 'group_by_is_company': True, 'exclude_contact': True, 'exclude_journal_item': True},
  625. # {'group_by_name': True, 'group_by_is_company': True, 'exclude_contact': True, 'exclude_journal_item': True}
  626. ]
  627. for merge_value in list_merge:
  628. id = self.create(cr, uid, merge_value, context=context)
  629. self.automatic_process_cb(cr, uid, [id], context=context)
  630. cr.execute("""
  631. UPDATE
  632. res_partner
  633. SET
  634. is_company = NULL
  635. WHERE
  636. parent_id IS NOT NULL AND
  637. is_company IS NOT NULL
  638. """)
  639. # cr.execute("""
  640. # UPDATE
  641. # res_partner as p1
  642. # SET
  643. # is_company = NULL,
  644. # parent_id = (
  645. # SELECT p2.id
  646. # FROM res_partner as p2
  647. # WHERE p2.email = p1.email AND
  648. # p2.parent_id != p2.id
  649. # LIMIT 1
  650. # )
  651. # WHERE
  652. # p1.parent_id = p1.id
  653. # """)
  654. return self._next_screen(cr, uid, this, context)
  655. def merge_pbp(self, cr, uid, partner_ids, dst_partner_id, context=None):
  656. self._merge(cr, uid, partner_ids, dst_partner_id, False,\
  657. context=context)
  658. return True
  659. def merge_cb(self, cr, uid, ids, context=None):
  660. assert is_integer_list(ids)
  661. context = dict(context or {}, active_test=False)
  662. this = self.browse(cr, uid, ids[0], context=context)
  663. partner_ids = set(map(int, this.partner_ids))
  664. if not partner_ids:
  665. this.write({'state': 'finished'})
  666. return {
  667. 'type': 'ir.actions.act_window',
  668. 'res_model': this._name,
  669. 'res_id': this.id,
  670. 'view_mode': 'form',
  671. 'target': 'new',
  672. }
  673. self._merge(cr, uid, partner_ids, this.dst_partner_id, context=context)
  674. if this.current_line_id:
  675. this.current_line_id.unlink()
  676. return self._next_screen(cr, uid, this, context)
  677. def auto_set_parent_id(self, cr, uid, ids, context=None):
  678. assert is_integer_list(ids)
  679. # select partner who have one least invoice
  680. partner_treated = ['@gmail.com']
  681. cr.execute(""" SELECT p.id, p.email
  682. FROM res_partner as p
  683. LEFT JOIN account_invoice as a
  684. ON p.id = a.partner_id AND a.state in ('open','paid')
  685. WHERE p.grade_id is NOT NULL
  686. GROUP BY p.id
  687. ORDER BY COUNT(a.id) DESC
  688. """)
  689. re_email = re.compile(r".*@")
  690. for id, email in cr.fetchall():
  691. # check email domain
  692. email = re_email.sub("@", email or "")
  693. if not email or email in partner_treated:
  694. continue
  695. partner_treated.append(email)
  696. # don't update the partners if they are more of one who have
  697. # invoice
  698. cr.execute(""" SELECT *
  699. FROM res_partner as p
  700. WHERE p.id != %s AND p.email LIKE '%%%s' AND
  701. EXISTS (SELECT * FROM account_invoice as a
  702. WHERE p.id = a.partner_id AND
  703. a.state in ('open','paid'))
  704. """ % (id, email))
  705. if len(cr.fetchall()) > 1:
  706. _logger.info("%s MORE OF ONE COMPANY", email)
  707. continue
  708. # to display changed values
  709. cr.execute(""" SELECT id,email
  710. FROM res_partner
  711. WHERE parent_id != %s AND id != %s
  712. AND email LIKE '%%%s'
  713. """ % (id, id, email))
  714. _logger.info("%r", cr.fetchall())
  715. # upgrade
  716. cr.execute(""" UPDATE res_partner
  717. SET parent_id = %s
  718. WHERE id != %s AND email LIKE '%%%s'
  719. """ % (id, id, email))
  720. return False