diff --git a/addons/crm_livechat/__manifest__.py b/addons/crm_livechat/__manifest__.py index d43908c47ea..91ee112bc14 100644 --- a/addons/crm_livechat/__manifest__.py +++ b/addons/crm_livechat/__manifest__.py @@ -7,6 +7,9 @@ 'summary': 'Create lead from livechat conversation', 'data': [ 'data/utm_data.xml', + 'data/crm_livechat_chatbot_data.xml', + 'views/chatbot_script_views.xml', + 'views/chatbot_script_step_views.xml', ], 'depends': [ 'crm', diff --git a/addons/crm_livechat/data/crm_livechat_chatbot_data.xml b/addons/crm_livechat/data/crm_livechat_chatbot_data.xml new file mode 100644 index 00000000000..a47411dd0ff --- /dev/null +++ b/addons/crm_livechat/data/crm_livechat_chatbot_data.xml @@ -0,0 +1,49 @@ + + + + + + + Lead Generation Bot + + + + + Hi there, what brings you to our website today? 👋 + 1 + free_input_multi + + + + + 2 + forward_operator + + + + + Hu-ho, it looks like none of our operators are available 🙁 + 3 + text + + + + + Would you mind leaving your email address so that we can reach you back? + 4 + question_email + + + + + Thank you, you should hear back from us very soon! + 5 + create_lead + + + + diff --git a/addons/crm_livechat/models/__init__.py b/addons/crm_livechat/models/__init__.py index 58536ecc3e7..c1a45883018 100644 --- a/addons/crm_livechat/models/__init__.py +++ b/addons/crm_livechat/models/__init__.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import chatbot_script +from . import chatbot_script_step from . import mail_channel diff --git a/addons/crm_livechat/models/chatbot_script.py b/addons/crm_livechat/models/chatbot_script.py new file mode 100644 index 00000000000..8fff92a94dd --- /dev/null +++ b/addons/crm_livechat/models/chatbot_script.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + + +class ChatbotScript(models.Model): + _inherit = 'chatbot.script' + + lead_count = fields.Integer( + string='Generated Lead Count', compute='_compute_lead_count') + + def _compute_lead_count(self): + mapped_leads = {} + if self.ids: + leads_data = self.env['crm.lead'].with_context(active_test=False).sudo()._read_group( + [('source_id', 'in', self.mapped('source_id').ids)], ['source_id'], ['source_id']) + mapped_leads = {lead['source_id'][0]: lead['source_id_count'] for lead in leads_data} + + for script in self: + script.lead_count = mapped_leads.get(script.source_id.id, 0) + + def action_view_leads(self): + self.ensure_one() + action = self.env['ir.actions.act_window']._for_xml_id('crm.crm_lead_all_leads') + action['domain'] = [('source_id', '=', self.source_id.id)] + action['context'] = {'create': False} + return action diff --git a/addons/crm_livechat/models/chatbot_script_step.py b/addons/crm_livechat/models/chatbot_script_step.py new file mode 100644 index 00000000000..0c9e21cecc6 --- /dev/null +++ b/addons/crm_livechat/models/chatbot_script_step.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import _, models, fields + + +class ChatbotScriptStep(models.Model): + _inherit = 'chatbot.script.step' + + step_type = fields.Selection( + selection_add=[('create_lead', 'Create Lead')], ondelete={'create_lead': 'cascade'}) + crm_team_id = fields.Many2one( + 'crm.team', string='Sales Team', ondelete='set null', + help="Used in combination with 'create_lead' step type in order to automatically " + "assign the created lead/opportunity to the defined team") + + def _chatbot_crm_prepare_lead_values(self, mail_channel, description): + return { + 'description': description + mail_channel._get_channel_history(), + 'name': _("%s's New Lead", self.chatbot_script_id.title), + 'source_id': self.chatbot_script_id.source_id.id, + 'team_id': self.crm_team_id.id, + 'type': 'lead' if self.crm_team_id.use_leads else 'opportunity', + 'user_id': False, + } + + def _process_step(self, mail_channel): + self.ensure_one() + + posted_message = super()._process_step(mail_channel) + + if self.step_type == 'create_lead': + self._process_step_create_lead(mail_channel) + + return posted_message + + def _process_step_create_lead(self, mail_channel): + """ When reaching a 'create_lead' step, we extract the relevant information: visitor's + email, phone and conversation history to create a crm.lead. + + We use the email and phone to update the environment partner's information (if not a public + user) if they differ from the current values. + + The whole conversation history will be saved into the lead's description for reference. + This also allows having a question of type 'free_input_multi' to let the visitor explain + his interest / needs before creating the lead. """ + + customer_values = self._chatbot_prepare_customer_values( + mail_channel, create_partner=False, update_partner=True) + if self.env.user._is_public(): + create_values = { + 'email_from': customer_values['email'], + 'phone': customer_values['phone'], + } + else: + partner = self.env.user.partner_id + create_values = { + 'partner_id': partner.id, + 'company_id': partner.company_id.id, + } + + create_values.update(self._chatbot_crm_prepare_lead_values( + mail_channel, customer_values['description'])) + + self.env['crm.lead'].create(create_values) diff --git a/addons/crm_livechat/models/mail_channel.py b/addons/crm_livechat/models/mail_channel.py index 4c5e6ef806f..51d3e905d9a 100644 --- a/addons/crm_livechat/models/mail_channel.py +++ b/addons/crm_livechat/models/mail_channel.py @@ -26,10 +26,6 @@ class MailChannel(models.Model): :param partner: internal user partner (operator) that created the lead; :param key: operator input in chat ('/lead Lead about Product') """ - description = ''.join( - '%s: %s
' % (message.author_id.name or self.anonymous_name, html2plaintext(message.body)) - for message in self.message_ids.sorted('id') - ) # converting message body back to plaintext for correct data formating in description # if public user is part of the chat: consider lead to be linked to an # anonymous user whatever the participants. Otherwise keep only share # partners (no user or portal user) to link to the lead. @@ -47,7 +43,7 @@ class MailChannel(models.Model): 'partner_id': customers[0].id if customers else False, 'user_id': False, 'team_id': False, - 'description': description, + 'description': self._get_channel_history(), 'referred': partner.name, 'source_id': utm_source and utm_source.id, }) diff --git a/addons/crm_livechat/tests/__init__.py b/addons/crm_livechat/tests/__init__.py index 3721104aa97..9219bc543f7 100644 --- a/addons/crm_livechat/tests/__init__.py +++ b/addons/crm_livechat/tests/__init__.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import chatbot_common +from . import test_chatbot_lead from . import test_crm_lead diff --git a/addons/crm_livechat/tests/chatbot_common.py b/addons/crm_livechat/tests/chatbot_common.py new file mode 100644 index 00000000000..9fc0ced2cda --- /dev/null +++ b/addons/crm_livechat/tests/chatbot_common.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.im_livechat.tests.chatbot_common import ChatbotCase +from odoo.addons.mail.tests.common import mail_new_test_user + + +class CrmChatbotCase(ChatbotCase): + + @classmethod + def setUpClass(cls): + super(CrmChatbotCase, cls).setUpClass() + + cls.company_id = cls.env['res.company'].create({ + 'name': 'Test Company', + 'country_id': cls.env.ref('base.be').id, + }) + + cls.user_public = mail_new_test_user( + cls.env, login='user_public', groups='base.group_public', name='Public User') + cls.user_portal = mail_new_test_user( + cls.env, login='user_portal', groups='base.group_portal', name='Portal User', + company_id=cls.company_id.id, email='portal@example.com') + # update company_id on partner since the user's company is not propagated + cls.user_portal.partner_id.write({'company_id': cls.company_id.id}) + + cls.sale_team = cls.env['crm.team'].create({ + 'name': 'Test Sale Team 1', + 'company_id': cls.company_id.id, + }) + + cls.sale_team_with_lead = cls.env['crm.team'].create({ + 'name': 'Test Sale Team 2', + 'use_leads': True, + 'company_id': cls.company_id.id, + }) + + cls.step_dispatch_create_lead = cls.env['chatbot.script.answer'].sudo().create({ + 'name': 'Create a lead', + 'script_step_id': cls.step_dispatch.id, + }) + + [ + cls.step_create_lead_email, + cls.step_create_lead_phone, + cls.step_create_lead, + ] = cls.env['chatbot.script.step'].sudo().create([{ + 'step_type': 'question_email', + 'message': 'Could you provide us your email please.', + 'sequence': 20, + 'triggering_answer_ids': [(4, cls.step_dispatch_create_lead.id)], + 'chatbot_script_id': cls.chatbot_script.id, + }, { + 'step_type': 'question_phone', + 'message': 'Could you also provide your phone please.', + 'sequence': 21, + 'triggering_answer_ids': [(4, cls.step_dispatch_create_lead.id)], + 'chatbot_script_id': cls.chatbot_script.id, + }, { + 'step_type': 'create_lead', + 'message': 'Thank you! A lead has been created.', + 'sequence': 22, + 'triggering_answer_ids': [(4, cls.step_dispatch_create_lead.id)], + 'crm_team_id': cls.sale_team.id, + 'chatbot_script_id': cls.chatbot_script.id, + }]) diff --git a/addons/crm_livechat/tests/test_chatbot_lead.py b/addons/crm_livechat/tests/test_chatbot_lead.py new file mode 100644 index 00000000000..35e7a1b3c2a --- /dev/null +++ b/addons/crm_livechat/tests/test_chatbot_lead.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.crm_livechat.tests import chatbot_common +from odoo.tests.common import users + + +class CrmChatbotCase(chatbot_common.CrmChatbotCase): + + @users('user_public') + def test_chatbot_lead_public_user(self): + self._chatbot_create_lead(self.user_public) + + created_lead = self.env['crm.lead'].sudo().search([], limit=1, order='id desc') + self.assertEqual(created_lead.name, "Testing Bot's New Lead") + self.assertEqual(created_lead.email_from, 'test2@example.com') + self.assertEqual(created_lead.phone, '123456') + + self.assertEqual(created_lead.team_id, self.sale_team) + self.assertEqual(created_lead.type, 'opportunity') + + @users('user_portal') + def test_chatbot_lead_portal_user(self): + self.step_create_lead.write({'crm_team_id': self.sale_team_with_lead}) + self._chatbot_create_lead(self.user_portal) + + created_lead = self.env['crm.lead'].sudo().search([], limit=1, order='id desc') + self.assertEqual(created_lead.name, "Testing Bot's New Lead") + self.assertNotEqual(created_lead.email_from, 'test2@example.com', "User's email should'nt have been overridden") + self.assertEqual(created_lead.phone, '123456', "User's phone should have been updated") + + self.assertEqual(created_lead.team_id, self.sale_team_with_lead) + self.assertEqual(created_lead.type, 'lead') + + def _chatbot_create_lead(self, user): + channel_info = self.livechat_channel._open_livechat_mail_channel( + anonymous_name='Test Visitor', chatbot_script=self.chatbot_script, user_id=user.id) + mail_channel = self.env['mail.channel'].sudo().browse(channel_info['id']) + + self._post_answer_and_trigger_next_step( + mail_channel, + self.step_dispatch_create_lead.name, + chatbot_script_answer=self.step_dispatch_create_lead + ) + self.assertEqual(mail_channel.chatbot_current_step_id, self.step_create_lead_email) + self._post_answer_and_trigger_next_step(mail_channel, 'test2@example.com') + + self.assertEqual(mail_channel.chatbot_current_step_id, self.step_create_lead_phone) + self._post_answer_and_trigger_next_step(mail_channel, '123456') + + self.assertEqual(mail_channel.chatbot_current_step_id, self.step_create_lead) diff --git a/addons/crm_livechat/views/chatbot_script_step_views.xml b/addons/crm_livechat/views/chatbot_script_step_views.xml new file mode 100644 index 00000000000..adfebca1eee --- /dev/null +++ b/addons/crm_livechat/views/chatbot_script_step_views.xml @@ -0,0 +1,15 @@ + + + + + chatbot.script.step.view.form.inherit.crm.livechat + chatbot.script.step + + + + + + + + + diff --git a/addons/crm_livechat/views/chatbot_script_views.xml b/addons/crm_livechat/views/chatbot_script_views.xml new file mode 100644 index 00000000000..5f556ac9232 --- /dev/null +++ b/addons/crm_livechat/views/chatbot_script_views.xml @@ -0,0 +1,18 @@ + + + + + chatbot.script.view.form.inherit.crm.livechat + chatbot.script + + +
+ +
+
+
+ +
diff --git a/addons/website_crm_livechat/models/__init__.py b/addons/website_crm_livechat/models/__init__.py index e81172ec3dd..61d3b3b91e7 100644 --- a/addons/website_crm_livechat/models/__init__.py +++ b/addons/website_crm_livechat/models/__init__.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import chatbot_script_step from . import crm_lead from . import mail_channel diff --git a/addons/website_crm_livechat/models/chatbot_script_step.py b/addons/website_crm_livechat/models/chatbot_script_step.py new file mode 100644 index 00000000000..27a23f7d49b --- /dev/null +++ b/addons/website_crm_livechat/models/chatbot_script_step.py @@ -0,0 +1,16 @@ + +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import _, models + + +class ChatbotScriptStep(models.Model): + _inherit = 'chatbot.script.step' + + def _chatbot_crm_prepare_lead_values(self, mail_channel, description): + values = super()._chatbot_crm_prepare_lead_values(mail_channel, description) + if mail_channel.livechat_visitor_id: + values['name'] = _("%s's New Lead", mail_channel.livechat_visitor_id.display_name) + values['visitor_ids'] = [(4, mail_channel.livechat_visitor_id.id)] + return values