mirror of
https://github.com/bringout/oca-ocb-mail.git
synced 2026-04-22 12:22:01 +02:00
19.0 vanilla
This commit is contained in:
parent
5df8c07b59
commit
daa394e8b0
2114 changed files with 564841 additions and 299642 deletions
|
|
@ -1,8 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, api, models, fields
|
||||
from odoo.tools import email_normalize, html2plaintext, is_html_empty, plaintext2html
|
||||
from odoo import api, Command, models, fields
|
||||
from odoo.http import request
|
||||
from odoo.tools import email_normalize, get_lang, html2plaintext, is_html_empty, plaintext2html
|
||||
from odoo.addons.mail.tools.discuss import Store
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ChatbotScript(models.Model):
|
||||
|
|
@ -20,21 +23,33 @@ class ChatbotScript(models.Model):
|
|||
script_step_ids = fields.One2many('chatbot.script.step', 'chatbot_script_id',
|
||||
copy=True, string='Script Steps')
|
||||
operator_partner_id = fields.Many2one('res.partner', string='Bot Operator',
|
||||
ondelete='restrict', required=True, copy=False)
|
||||
ondelete='restrict', required=True, copy=False, index=True)
|
||||
livechat_channel_count = fields.Integer(string='Livechat Channel Count', compute='_compute_livechat_channel_count')
|
||||
first_step_warning = fields.Selection([
|
||||
('first_step_operator', 'First Step Operator'),
|
||||
('first_step_invalid', 'First Step Invalid'),
|
||||
], compute="_compute_first_step_warning")
|
||||
|
||||
@api.constrains("script_step_ids")
|
||||
def _check_question_selection(self):
|
||||
for step in self.script_step_ids:
|
||||
if step.step_type == "question_selection" and not step.answer_ids:
|
||||
raise ValidationError(self.env._("Step of type 'Question' must have answers."))
|
||||
|
||||
@api.onchange("script_step_ids")
|
||||
def _onchange_script_step_ids(self):
|
||||
for step in self.script_step_ids:
|
||||
if step.step_type != "question_selection" and step.answer_ids:
|
||||
step.answer_ids = [Command.clear()]
|
||||
|
||||
def _compute_livechat_channel_count(self):
|
||||
channels_data = self.env['im_livechat.channel.rule'].read_group(
|
||||
[('chatbot_script_id', 'in', self.ids)], ['channel_id:count_distinct'], ['chatbot_script_id'])
|
||||
mapped_channels = {channel['chatbot_script_id'][0]: channel['channel_id'] for channel in channels_data}
|
||||
channels_data = self.env['im_livechat.channel.rule']._read_group(
|
||||
[('chatbot_script_id', 'in', self.ids)], ['chatbot_script_id'], ['channel_id:count_distinct'])
|
||||
mapped_channels = {chatbot_script.id: count_distinct for chatbot_script, count_distinct in channels_data}
|
||||
for script in self:
|
||||
script.livechat_channel_count = mapped_channels.get(script.id, 0)
|
||||
|
||||
@api.depends('script_step_ids.step_type')
|
||||
@api.depends("script_step_ids.is_forward_operator", "script_step_ids.step_type" )
|
||||
def _compute_first_step_warning(self):
|
||||
for script in self:
|
||||
allowed_first_step_types = [
|
||||
|
|
@ -45,14 +60,17 @@ class ChatbotScript(models.Model):
|
|||
'free_input_multi',
|
||||
]
|
||||
welcome_steps = script.script_step_ids and script._get_welcome_steps()
|
||||
if welcome_steps and welcome_steps[-1].step_type == 'forward_operator':
|
||||
if welcome_steps and welcome_steps[-1].is_forward_operator:
|
||||
script.first_step_warning = 'first_step_operator'
|
||||
elif welcome_steps and welcome_steps[-1].step_type not in allowed_first_step_types:
|
||||
script.first_step_warning = 'first_step_invalid'
|
||||
else:
|
||||
script.first_step_warning = False
|
||||
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def copy_data(self, default=None):
|
||||
vals_list = super().copy_data(default=default)
|
||||
return [dict(vals, title=self.env._("%s (copy)", script.title)) for script, vals in zip(self, vals_list)]
|
||||
|
||||
def copy(self, default=None):
|
||||
""" Correctly copy the 'triggering_answer_ids' field from the original script_step_ids to the clone.
|
||||
This needs to be done in post-processing to make sure we get references to the newly created
|
||||
|
|
@ -60,35 +78,32 @@ class ChatbotScript(models.Model):
|
|||
|
||||
This implementation assumes that the order of created steps and answers will be kept between
|
||||
the original and the clone, using 'zip()' to match the records between the two. """
|
||||
|
||||
default = default or {}
|
||||
default['title'] = self.title + _(' (copy)')
|
||||
|
||||
clone_chatbot_script = super().copy(default=default)
|
||||
new_scripts = super().copy(default=default)
|
||||
if 'question_ids' in default:
|
||||
return clone_chatbot_script
|
||||
return new_scripts
|
||||
|
||||
original_steps = self.script_step_ids.sorted()
|
||||
clone_steps = clone_chatbot_script.script_step_ids.sorted()
|
||||
for old_script, new_script in zip(self, new_scripts):
|
||||
original_steps = old_script.script_step_ids.sorted()
|
||||
clone_steps = new_script.script_step_ids.sorted()
|
||||
|
||||
answers_map = {}
|
||||
for clone_step, original_step in zip(clone_steps, original_steps):
|
||||
for clone_answer, original_answer in zip(clone_step.answer_ids.sorted(), original_step.answer_ids.sorted()):
|
||||
answers_map[original_answer] = clone_answer
|
||||
answers_map = {}
|
||||
for clone_step, original_step in zip(clone_steps, original_steps):
|
||||
for clone_answer, original_answer in zip(clone_step.answer_ids.sorted(), original_step.answer_ids.sorted()):
|
||||
answers_map[original_answer] = clone_answer
|
||||
|
||||
for clone_step, original_step in zip(clone_steps, original_steps):
|
||||
clone_step.write({
|
||||
'triggering_answer_ids': [
|
||||
(4, answer.id)
|
||||
for answer in [
|
||||
answers_map[original_answer]
|
||||
for original_answer
|
||||
in original_step.triggering_answer_ids
|
||||
for clone_step, original_step in zip(clone_steps, original_steps):
|
||||
clone_step.write({
|
||||
'triggering_answer_ids': [
|
||||
(4, answer.id)
|
||||
for answer in [
|
||||
answers_map[original_answer]
|
||||
for original_answer
|
||||
in original_step.triggering_answer_ids
|
||||
]
|
||||
]
|
||||
]
|
||||
})
|
||||
|
||||
return clone_chatbot_script
|
||||
})
|
||||
return new_scripts
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
|
@ -137,7 +152,7 @@ class ChatbotScript(models.Model):
|
|||
end user.
|
||||
|
||||
This is important because we need to display those welcoming steps in a special fashion on
|
||||
the frontend, since those are not inserted into the mail.channel as actual mail.messages,
|
||||
the frontend, since those are not inserted into the discuss.channel as actual mail.messages,
|
||||
to avoid bloating the channels with bot messages if the end-user never interacts with it. """
|
||||
self.ensure_one()
|
||||
|
||||
|
|
@ -149,23 +164,23 @@ class ChatbotScript(models.Model):
|
|||
|
||||
return welcome_steps
|
||||
|
||||
def _post_welcome_steps(self, mail_channel):
|
||||
def _post_welcome_steps(self, discuss_channel):
|
||||
""" Welcome messages are only posted after the visitor's first interaction with the chatbot.
|
||||
See 'chatbot.script#_get_welcome_steps()' for more details.
|
||||
|
||||
Side note: it is important to set the 'chatbot_current_step_id' on each iteration so that
|
||||
it's correctly set when going into 'mail_channel#_message_post_after_hook()'. """
|
||||
it's correctly set when going into 'discuss_channel#_message_post_after_hook()'. """
|
||||
|
||||
self.ensure_one()
|
||||
posted_messages = self.env['mail.message']
|
||||
|
||||
for welcome_step in self._get_welcome_steps():
|
||||
mail_channel.chatbot_current_step_id = welcome_step.id
|
||||
discuss_channel.chatbot_current_step_id = welcome_step.id
|
||||
|
||||
if not is_html_empty(welcome_step.message):
|
||||
posted_messages += mail_channel.with_context(mail_create_nosubscribe=True).message_post(
|
||||
posted_messages += discuss_channel.with_context(mail_post_autofollow_author_skip=True).message_post(
|
||||
author_id=self.operator_partner_id.id,
|
||||
body=plaintext2html(welcome_step.message),
|
||||
body=plaintext2html(welcome_step.message, with_paragraph=False),
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
)
|
||||
|
|
@ -182,35 +197,29 @@ class ChatbotScript(models.Model):
|
|||
# Tooling / Misc
|
||||
# --------------------------
|
||||
|
||||
def _format_for_frontend(self):
|
||||
""" Small utility method that formats the script into a dict usable by the frontend code. """
|
||||
self.ensure_one()
|
||||
def _to_store_defaults(self, target):
|
||||
return [Store.One("operator_partner_id", ["name"]), "title"]
|
||||
|
||||
return {
|
||||
'chatbot_script_id': self.id,
|
||||
'chatbot_name': self.title,
|
||||
'chatbot_operator_partner_id': self.operator_partner_id.id,
|
||||
'chatbot_welcome_steps': [
|
||||
step._format_for_frontend()
|
||||
for step in self._get_welcome_steps()
|
||||
]
|
||||
}
|
||||
|
||||
def _validate_email(self, email_address, mail_channel):
|
||||
def _validate_email(self, email_address, discuss_channel):
|
||||
email_address = html2plaintext(email_address)
|
||||
email_normalized = email_normalize(email_address)
|
||||
|
||||
posted_message = False
|
||||
error_message = False
|
||||
if not email_normalized:
|
||||
error_message = _(
|
||||
error_message = self.env._(
|
||||
"'%(input_email)s' does not look like a valid email. Can you please try again?",
|
||||
input_email=email_address
|
||||
)
|
||||
posted_message = mail_channel._chatbot_post_message(self, plaintext2html(error_message))
|
||||
posted_message = discuss_channel._chatbot_post_message(self, plaintext2html(error_message))
|
||||
|
||||
return {
|
||||
'success': bool(email_normalized),
|
||||
'posted_message': posted_message,
|
||||
'error_message': error_message,
|
||||
}
|
||||
|
||||
def _get_chatbot_language(self):
|
||||
return get_lang(
|
||||
self.env, lang_code=request and request.httprequest.cookies.get("frontend_lang")
|
||||
).code
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue