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.

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