[IMP] crm_livechat: allow creating leads within the chatbot

PURPOSE

This commits adds a special step_type on the chatbot.script.steps that allows
creating a crm.lead when reaching it.

SPECS

In addition, we let the end user configure in which crm.team that lead will
land upon being created, making it possible to sort your leads based on
previous questions.
e.g: "In which country do you live"
And based on the answer, you assign to a different sales.team.

This commit comes with the necessary business code to make this feature work
as well as some extra testing methods that ensure it is correctly implemented.

Task-2030386

Part-of: odoo/odoo#84000
Co-authored-by: Patrick Hoste <pko@odoo.com>
Co-authored-by: Aurélien Warnon <awa@odoo.com>
This commit is contained in:
Aurélien Warnon
2022-04-01 18:49:56 +02:00
co-authored by Patrick Hoste
parent ea680d8595
commit b0991dc110
13 changed files with 317 additions and 5 deletions
+3
View File
@@ -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',
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data noupdate="1">
<!--
This provides a working lead generation chatbot for people to work with.
It's placed into 'data' to give them a starting point.
From that record, they can duplicate / adapt / delete / ...
-->
<record id="chatbot_script_lead_generation_bot" model="chatbot.script">
<field name="title">Lead Generation Bot</field>
<field name="image_1920" type="base64" file="mail/static/src/img/odoobot_transparent.png"/>
</record>
<record id="chatbot_script_lead_generation_step_welcome" model="chatbot.script.step">
<field name="message">Hi there, what brings you to our website today? 👋</field>
<field name="sequence">1</field>
<field name="step_type">free_input_multi</field>
<field name="chatbot_script_id" ref="chatbot_script_lead_generation_bot"/>
</record>
<record id="chatbot_script_lead_generation_step_forward_operator" model="chatbot.script.step">
<field name="sequence">2</field>
<field name="step_type">forward_operator</field>
<field name="chatbot_script_id" ref="chatbot_script_lead_generation_bot"/>
</record>
<record id="chatbot_script_lead_generation_step_noone_available" model="chatbot.script.step">
<field name="message">Hu-ho, it looks like none of our operators are available 🙁</field>
<field name="sequence">3</field>
<field name="step_type">text</field>
<field name="chatbot_script_id" ref="chatbot_script_lead_generation_bot"/>
</record>
<record id="chatbot_script_welcome_step_pricing_email" model="chatbot.script.step">
<field name="message">Would you mind leaving your email address so that we can reach you back?</field>
<field name="sequence">4</field>
<field name="step_type">question_email</field>
<field name="chatbot_script_id" ref="chatbot_script_lead_generation_bot"/>
</record>
<record id="chatbot_script_welcome_step_just_looking" model="chatbot.script.step">
<field name="message">Thank you, you should hear back from us very soon!</field>
<field name="sequence">5</field>
<field name="step_type">create_lead</field>
<field name="chatbot_script_id" ref="chatbot_script_lead_generation_bot"/>
</record>
</data></odoo>
+2
View File
@@ -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
@@ -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
@@ -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)
+1 -5
View File
@@ -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<br/>' % (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,
})
+2
View File
@@ -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
@@ -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,
}])
@@ -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)
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="chatbot_script_step_view_form" model="ir.ui.view">
<field name="name">chatbot.script.step.view.form.inherit.crm.livechat</field>
<field name="model">chatbot.script.step</field>
<field name="inherit_id" ref="im_livechat.chatbot_script_step_view_form"/>
<field name="arch" type="xml">
<field name="step_type" position="after">
<field name="crm_team_id" attrs="{'invisible': [('step_type', '!=', 'create_lead')]}"/>
</field>
</field>
</record>
</data></odoo>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo><data>
<record id="chatbot_script_view_form" model="ir.ui.view">
<field name="name">chatbot.script.view.form.inherit.crm.livechat</field>
<field name="model">chatbot.script</field>
<field name="inherit_id" ref="im_livechat.chatbot_script_view_form"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="action_view_leads" type="object" class="oe_stat_button"
icon="fa-star" attrs="{'invisible': [('lead_count', '=', 0)]}">
<field string="Leads" name="lead_count" widget="statinfo" />
</button>
</div>
</field>
</record>
</data></odoo>
@@ -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
@@ -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