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.

915 lines
33 KiB

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