diff --git a/addons/crm/data/crm_lead_merge_template.xml b/addons/crm/data/crm_lead_merge_template.xml
index 3255e81123f..a46087183db 100644
--- a/addons/crm/data/crm_lead_merge_template.xml
+++ b/addons/crm/data/crm_lead_merge_template.xml
@@ -122,7 +122,7 @@
-
Marketing:
@@ -137,6 +137,22 @@
Source:
+
+
+
+ The contacts below have been added as followers of this lead
+ because they have been contacted less than 30 days ago on
+ .
+
+
+
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index e58c95e5b6c..0e5ce514c78 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -15,7 +15,7 @@ from odoo.addons.phone_validation.tools import phone_validation
from odoo.exceptions import UserError, AccessError
from odoo.osv import expression
from odoo.tools.translate import _
-from odoo.tools import date_utils, email_re, email_split, is_html_empty
+from odoo.tools import date_utils, email_re, email_split, is_html_empty, groupby
from . import crm_stage
@@ -1296,10 +1296,13 @@ class Lead(models.Model):
if team_id:
merged_data['team_id'] = team_id
+ merged_followers = opportunities_head._merge_followers(opportunities_tail)
+
# log merge message
opportunities_head.message_post_with_view(
"crm.crm_lead_merge_summary",
values={
+ "merged_followers": merged_followers,
"opportunities": opportunities_tail,
"is_html_empty": is_html_empty
},
@@ -1434,6 +1437,50 @@ class Lead(models.Model):
'opportunity_id': self.id,
})
+ def _merge_followers(self, opportunities):
+ """Add the followers into the destination lead if they post a message in the last 30 days.
+
+ :param opportunities : Record of opportunities to transfer
+ :return: {old_lead_id: Record} Followers which have been added in
+ the destination lead grouped by source lead ID.
+ """
+ self.ensure_one()
+
+ self.env['mail.message'].flush()
+ self.env['mail.followers'].flush()
+
+ # Get the active followers (followers whose partner post a message on the
+ # leads in the last 30 days) which should be moved on the destination lead
+ self.env.cr.execute(
+ '''
+ SELECT MAX(mf.id) AS id
+ FROM mail_followers AS mf
+ JOIN mail_message AS mm
+ ON mm.author_id = mf.partner_id
+ AND mm.res_id = mf.res_id
+ AND mm.model = 'crm.lead'
+ AND mm.date > NOW() - INTERVAL '30 DAY'
+ /* Check if the partner is already
+ following the destination lead */
+ LEFT JOIN mail_followers AS destf
+ ON destf.res_model = 'crm.lead'
+ AND destf.res_id = %(lead_id)s
+ AND destf.partner_id = mf.partner_id
+ /* Select only once each partner
+ to not create duplicated followers */
+ WHERE mf.res_model = 'crm.lead'
+ AND mf.res_id IN %(lead_ids)s
+ AND destf IS NULL
+ GROUP BY mf.partner_id
+ ''',
+ {'lead_ids': tuple(opportunities.ids), 'lead_id': self.id},
+ )
+ followers_to_update = [r[0] for r in self.env.cr.fetchall()]
+ followers_to_update = self.env['mail.followers'].browse(followers_to_update).sudo()
+ followers_by_old_lead = dict(groupby(followers_to_update, lambda f: f.res_id))
+ followers_to_update.write({'res_id': self.id})
+ return followers_by_old_lead
+
# CONVERT
# ----------------------------------------------------------------------
diff --git a/addons/crm/tests/test_crm_lead_merge.py b/addons/crm/tests/test_crm_lead_merge.py
index f936904d174..bbf73b70155 100644
--- a/addons/crm/tests/test_crm_lead_merge.py
+++ b/addons/crm/tests/test_crm_lead_merge.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
+from datetime import timedelta
from odoo.addons.crm.tests.common import TestLeadConvertMassCommon
from odoo.fields import Datetime
@@ -391,3 +392,97 @@ class TestLeadMerge(TestLeadMergeCommon):
# 2many accessors updated
self.assertEqual(master_lead.activity_ids, activity)
self.assertEqual(master_lead.calendar_event_ids, calendar_event)
+
+ @users('user_sales_manager')
+ @mute_logger('odoo.models.unlink')
+ def test_merge_method_followers(self):
+ """ Test that the followers of the leads are added in the destination lead.
+
+ They should be added if:
+ - The related partner was active on the lead (posted a message in the last 30 days)
+ - The related partner is not already following the destination lead
+
+ Leads Followers Info
+ ---------------------------------------------------------------------------------
+ lead_w_contact contact_1 OK (destination lead)
+ lead_w_email contact_1 KO (already following the destination lead)
+ contact_2 OK (active on lead_w_email)
+ contact_company KO (most recent message on lead_w_email is 35 days ago, message
+ on lead_w_partner is not counted as he doesn't follow it)
+ lead_w_partner contact_2 KO (already added with lead_w_email)
+ lead_w_partner_company
+ """
+ self.leads.message_follower_ids.unlink()
+ self.leads.message_ids.unlink()
+
+ self.lead_w_contact.message_subscribe([self.contact_1.id])
+ self.lead_w_email.message_subscribe([self.contact_1.id, self.contact_2.id, self.contact_company.id])
+ self.lead_w_partner.message_subscribe([self.contact_2.id])
+
+ self.env['mail.message'].create([{
+ 'author_id': self.contact_1.id,
+ 'model': 'crm.lead',
+ 'res_id': self.lead_w_contact.id,
+ 'date': Datetime.now() - timedelta(days=1),
+ 'subtype_id': self.ref('mail.mt_comment'),
+ 'reply_to': False,
+ 'body': 'Test follower',
+ }, {
+ 'author_id': self.contact_1.id,
+ 'model': 'crm.lead',
+ 'res_id': self.lead_w_email.id,
+ 'date': Datetime.now() - timedelta(days=20),
+ 'subtype_id': self.ref('mail.mt_comment'),
+ 'reply_to': False,
+ 'body': 'Test follower',
+ }, {
+ 'author_id': self.contact_2.id,
+ 'model': 'crm.lead',
+ 'res_id': self.lead_w_email.id,
+ 'date': Datetime.now() - timedelta(days=15),
+ 'subtype_id': self.ref('mail.mt_comment'),
+ 'reply_to': False,
+ 'body': 'Test follower',
+ }, {
+ 'author_id': self.contact_2.id,
+ 'model': 'crm.lead',
+ 'res_id': self.lead_w_partner.id,
+ 'date': Datetime.now() - timedelta(days=29),
+ 'subtype_id': self.ref('mail.mt_comment'),
+ 'reply_to': False,
+ 'body': 'Test follower',
+ }, {
+ 'author_id': self.contact_company.id,
+ 'model': 'crm.lead',
+ 'res_id': self.lead_w_email.id,
+ 'date': Datetime.now() - timedelta(days=35),
+ 'subtype_id': self.ref('mail.mt_comment'),
+ 'reply_to': False,
+ 'body': 'Test follower',
+ }, {
+ 'author_id': self.contact_company.id,
+ 'model': 'crm.lead',
+ 'res_id': self.lead_w_partner.id,
+ 'date': Datetime.now(),
+ 'subtype_id': self.ref('mail.mt_comment'),
+ 'reply_to': False,
+ 'body': 'Test follower',
+ }])
+ initial_followers = self.lead_w_contact.message_follower_ids
+
+ leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
+ master_lead = leads._merge_opportunity(max_length=None)
+
+ self.assertEqual(master_lead, self.lead_w_contact)
+
+ # Check followers of the destination lead
+ new_partner_followers = (master_lead.message_follower_ids - initial_followers).partner_id
+ self.assertIn(self.contact_2, new_partner_followers,
+ 'The partner must follow the destination lead')
+ # "contact_company" posted a message 35 days ago on lead_2, so it's considered as inactive
+ # "contact_company" posted a message now on lead_3, but he doesn't follow lead_3
+ # so this message is just ignored
+ self.assertNotIn(self.contact_company, new_partner_followers,
+ 'The partner was not active on the lead')
+ self.assertIn(self.contact_1, master_lead.message_follower_ids.partner_id,
+ 'Should not have removed follower of the destination lead')