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')