summaryrefslogtreecommitdiff
path: root/addons/website_livechat/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/website_livechat/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/website_livechat/models')
-rw-r--r--addons/website_livechat/models/__init__.py9
-rw-r--r--addons/website_livechat/models/im_livechat.py19
-rw-r--r--addons/website_livechat/models/im_livechat_channel.py23
-rw-r--r--addons/website_livechat/models/ir_http.py13
-rw-r--r--addons/website_livechat/models/mail_channel.py76
-rw-r--r--addons/website_livechat/models/res_config_settings.py10
-rw-r--r--addons/website_livechat/models/website.py63
-rw-r--r--addons/website_livechat/models/website_visitor.py120
8 files changed, 333 insertions, 0 deletions
diff --git a/addons/website_livechat/models/__init__.py b/addons/website_livechat/models/__init__.py
new file mode 100644
index 00000000..7da0d975
--- /dev/null
+++ b/addons/website_livechat/models/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+from . import im_livechat
+from . import im_livechat_channel
+from . import ir_http
+from . import mail_channel
+from . import res_config_settings
+from . import website
+from . import website_visitor
diff --git a/addons/website_livechat/models/im_livechat.py b/addons/website_livechat/models/im_livechat.py
new file mode 100644
index 00000000..5e3bdbab
--- /dev/null
+++ b/addons/website_livechat/models/im_livechat.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models, fields
+from odoo.addons.http_routing.models.ir_http import slug
+from odoo.tools.translate import html_translate
+
+
+class ImLivechatChannel(models.Model):
+
+ _name = 'im_livechat.channel'
+ _inherit = ['im_livechat.channel', 'website.published.mixin']
+
+ def _compute_website_url(self):
+ super(ImLivechatChannel, self)._compute_website_url()
+ for channel in self:
+ channel.website_url = "/livechat/channel/%s" % (slug(channel),)
+
+ website_description = fields.Html("Website description", default=False, help="Description of the channel displayed on the website page", sanitize_attributes=False, translate=html_translate, sanitize_form=False)
diff --git a/addons/website_livechat/models/im_livechat_channel.py b/addons/website_livechat/models/im_livechat_channel.py
new file mode 100644
index 00000000..fe4b46b0
--- /dev/null
+++ b/addons/website_livechat/models/im_livechat_channel.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, _
+
+
+class ImLivechatChannel(models.Model):
+ _inherit = 'im_livechat.channel'
+
+ def _get_livechat_mail_channel_vals(self, anonymous_name, operator, user_id=None, country_id=None):
+ mail_channel_vals = super(ImLivechatChannel, self)._get_livechat_mail_channel_vals(anonymous_name, operator, user_id=user_id, country_id=country_id)
+ visitor_sudo = self.env['website.visitor']._get_visitor_from_request()
+ if visitor_sudo:
+ mail_channel_vals['livechat_visitor_id'] = visitor_sudo.id
+ if not user_id:
+ mail_channel_vals['anonymous_name'] = visitor_sudo.display_name + (' (%s)' % visitor_sudo.country_id.name if visitor_sudo.country_id else '')
+ # As chat requested by the visitor, delete the chat requested by an operator if any to avoid conflicts between two flows
+ # TODO DBE : Move this into the proper method (open or init mail channel)
+ chat_request_channel = self.env['mail.channel'].sudo().search([('livechat_visitor_id', '=', visitor_sudo.id), ('livechat_active', '=', True)])
+ for mail_channel in chat_request_channel:
+ mail_channel._close_livechat_session(cancel=True, operator=operator.name)
+
+ return mail_channel_vals
diff --git a/addons/website_livechat/models/ir_http.py b/addons/website_livechat/models/ir_http.py
new file mode 100644
index 00000000..20f54548
--- /dev/null
+++ b/addons/website_livechat/models/ir_http.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class IrHttp(models.AbstractModel):
+ _inherit = 'ir.http'
+
+ @classmethod
+ def _get_translation_frontend_modules_name(cls):
+ mods = super(IrHttp, cls)._get_translation_frontend_modules_name()
+ return mods + ['im_livechat']
diff --git a/addons/website_livechat/models/mail_channel.py b/addons/website_livechat/models/mail_channel.py
new file mode 100644
index 00000000..8d6f9196
--- /dev/null
+++ b/addons/website_livechat/models/mail_channel.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+
+
+class MailChannel(models.Model):
+ _inherit = 'mail.channel'
+
+ livechat_visitor_id = fields.Many2one('website.visitor', string='Visitor')
+
+ def _execute_channel_pin(self, pinned=False):
+ """ Override to clean an empty livechat channel.
+ This is typically called when the operator send a chat request to a website.visitor
+ but don't speak to him and closes the chatter.
+ This allows operators to send the visitor a new chat request.
+ If active empty livechat channel,
+ delete mail_channel as not useful to keep empty chat
+ """
+ super(MailChannel, self)._execute_channel_pin(pinned)
+ if self.livechat_active and not self.channel_message_ids:
+ self.unlink()
+
+ def channel_info(self, extra_info=False):
+ """
+ Override to add visitor information on the mail channel infos.
+ This will be used to display a banner with visitor informations
+ at the top of the livechat channel discussion view in discuss module.
+ """
+ channel_infos = super(MailChannel, self).channel_info(extra_info)
+ channel_infos_dict = dict((c['id'], c) for c in channel_infos)
+ for channel in self:
+ visitor = channel.livechat_visitor_id
+ if visitor:
+ channel_infos_dict[channel.id]['visitor'] = {
+ 'name': visitor.display_name,
+ 'country_code': visitor.country_id.code.lower() if visitor.country_id else False,
+ 'country_id': visitor.country_id.id,
+ 'is_connected': visitor.is_connected,
+ 'history': self.sudo()._get_visitor_history(visitor),
+ 'website': visitor.website_id.name,
+ 'lang': visitor.lang_id.name,
+ 'partner_id': visitor.partner_id.id,
+ }
+ return list(channel_infos_dict.values())
+
+ def _get_visitor_history(self, visitor):
+ """
+ Prepare history string to render it in the visitor info div on discuss livechat channel view.
+ :param visitor: website.visitor of the channel
+ :return: arrow separated string containing navigation history information
+ """
+ recent_history = self.env['website.track'].search([('page_id', '!=', False), ('visitor_id', '=', visitor.id)], limit=3)
+ return ' → '.join(visit.page_id.name + ' (' + visit.visit_datetime.strftime('%H:%M') + ')' for visit in reversed(recent_history))
+
+ def _get_visitor_leave_message(self, operator=False, cancel=False):
+ name = _('The visitor') if not self.livechat_visitor_id else self.livechat_visitor_id.display_name
+ if cancel:
+ message = _("""%s has started a conversation with %s.
+ The chat request has been canceled.""") % (name, operator or _('an operator'))
+ else:
+ message = _('%s has left the conversation.', name)
+
+ return message
+
+ @api.returns('mail.message', lambda value: value.id)
+ def message_post(self, **kwargs):
+ """Override to mark the visitor as still connected.
+ If the message sent is not from the operator (so if it's the visitor or
+ odoobot sending closing chat notification, the visitor last action date is updated."""
+ message = super(MailChannel, self).message_post(**kwargs)
+ message_author_id = message.author_id
+ visitor = self.livechat_visitor_id
+ if len(self) == 1 and visitor and message_author_id != self.livechat_operator_id:
+ visitor._update_visitor_last_visit()
+ return message
diff --git a/addons/website_livechat/models/res_config_settings.py b/addons/website_livechat/models/res_config_settings.py
new file mode 100644
index 00000000..9c47f28b
--- /dev/null
+++ b/addons/website_livechat/models/res_config_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class ResConfigSettings(models.TransientModel):
+ _inherit = 'res.config.settings'
+
+ channel_id = fields.Many2one('im_livechat.channel', string='Website Live Channel', related='website_id.channel_id', readonly=False)
diff --git a/addons/website_livechat/models/website.py b/addons/website_livechat/models/website.py
new file mode 100644
index 00000000..a4bd8b15
--- /dev/null
+++ b/addons/website_livechat/models/website.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models, _
+from odoo.addons.http_routing.models.ir_http import url_for
+
+
+class Website(models.Model):
+
+ _inherit = "website"
+
+ channel_id = fields.Many2one('im_livechat.channel', string='Website Live Chat Channel')
+
+ def get_livechat_channel_info(self):
+ """ Get the livechat info dict (button text, channel name, ...) for the livechat channel of
+ the current website.
+ """
+ self.ensure_one()
+ if self.channel_id:
+ livechat_info = self.channel_id.sudo().get_livechat_info()
+ if livechat_info['available']:
+ livechat_request_session = self._get_livechat_request_session()
+ if livechat_request_session:
+ livechat_info['options']['chat_request_session'] = livechat_request_session
+ return livechat_info
+ return {}
+
+ def _get_livechat_request_session(self):
+ """
+ Check if there is an opened chat request for the website livechat channel and the current visitor (from request).
+ If so, prepare the livechat session information that will be stored in visitor's cookies
+ and used by livechat widget to directly open this session instead of allowing the visitor to
+ initiate a new livechat session.
+ :param {int} channel_id: channel
+ :return: {dict} livechat request session information
+ """
+ visitor = self.env['website.visitor']._get_visitor_from_request()
+ if visitor:
+ # get active chat_request linked to visitor
+ chat_request_channel = self.env['mail.channel'].sudo().search([
+ ('livechat_visitor_id', '=', visitor.id),
+ ('livechat_channel_id', '=', self.channel_id.id),
+ ('livechat_active', '=', True),
+ ('channel_message_ids', '!=', False)
+ ], order='create_date desc', limit=1)
+ if chat_request_channel:
+ return {
+ "folded": False,
+ "id": chat_request_channel.id,
+ "operator_pid": [
+ chat_request_channel.livechat_operator_id.id,
+ chat_request_channel.livechat_operator_id.display_name
+ ],
+ "name": chat_request_channel.name,
+ "uuid": chat_request_channel.uuid,
+ "type": "chat_request"
+ }
+ return {}
+
+ def get_suggested_controllers(self):
+ suggested_controllers = super(Website, self).get_suggested_controllers()
+ suggested_controllers.append((_('Live Support'), url_for('/livechat'), 'website_livechat'))
+ return suggested_controllers
diff --git a/addons/website_livechat/models/website_visitor.py b/addons/website_livechat/models/website_visitor.py
new file mode 100644
index 00000000..46731aea
--- /dev/null
+++ b/addons/website_livechat/models/website_visitor.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import datetime, timedelta
+import json
+
+from odoo import models, api, fields, _
+from odoo.exceptions import UserError
+from odoo.http import request
+
+
+class WebsiteVisitor(models.Model):
+ _inherit = 'website.visitor'
+
+ livechat_operator_id = fields.Many2one('res.partner', compute='_compute_livechat_operator_id', store=True, string='Speaking with')
+ livechat_operator_name = fields.Char('Operator Name', related="livechat_operator_id.name")
+ mail_channel_ids = fields.One2many('mail.channel', 'livechat_visitor_id',
+ string="Visitor's livechat channels", readonly=True)
+ session_count = fields.Integer('# Sessions', compute="_compute_session_count")
+
+ @api.depends('mail_channel_ids.livechat_active', 'mail_channel_ids.livechat_operator_id')
+ def _compute_livechat_operator_id(self):
+ results = self.env['mail.channel'].search_read(
+ [('livechat_visitor_id', 'in', self.ids), ('livechat_active', '=', True)],
+ ['livechat_visitor_id', 'livechat_operator_id']
+ )
+ visitor_operator_map = {int(result['livechat_visitor_id'][0]): int(result['livechat_operator_id'][0]) for result in results}
+ for visitor in self:
+ visitor.livechat_operator_id = visitor_operator_map.get(visitor.id, False)
+
+ @api.depends('mail_channel_ids')
+ def _compute_session_count(self):
+ sessions = self.env['mail.channel'].search([('livechat_visitor_id', 'in', self.ids)])
+ session_count = dict.fromkeys(self.ids, 0)
+ for session in sessions.filtered(lambda c: c.channel_message_ids):
+ session_count[session.livechat_visitor_id.id] += 1
+ for visitor in self:
+ visitor.session_count = session_count.get(visitor.id, 0)
+
+ def action_send_chat_request(self):
+ """ Send a chat request to website_visitor(s).
+ This creates a chat_request and a mail_channel with livechat active flag.
+ But for the visitor to get the chat request, the operator still has to speak to the visitor.
+ The visitor will receive the chat request the next time he navigates to a website page.
+ (see _handle_webpage_dispatch for next step)"""
+ # check if visitor is available
+ unavailable_visitors_count = self.env['mail.channel'].search_count([('livechat_visitor_id', 'in', self.ids), ('livechat_active', '=', True)])
+ if unavailable_visitors_count:
+ raise UserError(_('Recipients are not available. Please refresh the page to get latest visitors status.'))
+ # check if user is available as operator
+ for website in self.mapped('website_id'):
+ if not website.channel_id:
+ raise UserError(_('No Livechat Channel allows you to send a chat request for website %s.', website.name))
+ self.website_id.channel_id.write({'user_ids': [(4, self.env.user.id)]})
+ # Create chat_requests and linked mail_channels
+ mail_channel_vals_list = []
+ for visitor in self:
+ operator = self.env.user
+ country = visitor.country_id
+ visitor_name = "%s (%s)" % (visitor.display_name, country.name) if country else visitor.display_name
+ channel_partner_to_add = [(4, operator.partner_id.id)]
+ if visitor.partner_id:
+ channel_partner_to_add.append((4, visitor.partner_id.id))
+ else:
+ channel_partner_to_add.append((4, self.env.ref('base.public_partner').id))
+ mail_channel_vals_list.append({
+ 'channel_partner_ids': channel_partner_to_add,
+ 'livechat_channel_id': visitor.website_id.channel_id.id,
+ 'livechat_operator_id': self.env.user.partner_id.id,
+ 'channel_type': 'livechat',
+ 'public': 'private',
+ 'email_send': False,
+ 'country_id': country.id,
+ 'anonymous_name': visitor_name,
+ 'name': ', '.join([visitor_name, operator.livechat_username if operator.livechat_username else operator.name]),
+ 'livechat_visitor_id': visitor.id,
+ 'livechat_active': True,
+ })
+ if mail_channel_vals_list:
+ mail_channels = self.env['mail.channel'].create(mail_channel_vals_list)
+ # Open empty chatter to allow the operator to start chatting with the visitor.
+ values = {
+ 'fold_state': 'open',
+ 'is_minimized': True,
+ }
+ mail_channels_uuid = mail_channels.mapped('uuid')
+ domain = [('partner_id', '=', self.env.user.partner_id.id), ('channel_id.uuid', 'in', mail_channels_uuid)]
+ channel_partners = self.env['mail.channel.partner'].search(domain)
+ channel_partners.write(values)
+ mail_channels_info = mail_channels.channel_info('send_chat_request')
+ notifications = []
+ for mail_channel_info in mail_channels_info:
+ notifications.append([(self._cr.dbname, 'res.partner', operator.partner_id.id), mail_channel_info])
+ self.env['bus.bus'].sendmany(notifications)
+
+ def _link_to_visitor(self, target, keep_unique=True):
+ """ Copy sessions of the secondary visitors to the main partner visitor. """
+ if target.partner_id:
+ target.mail_channel_ids |= self.mail_channel_ids
+ super(WebsiteVisitor, self)._link_to_visitor(target, keep_unique=keep_unique)
+
+ def _link_to_partner(self, partner, update_values=None):
+ """ Adapt partner in members of related livechats """
+ if partner:
+ self.mail_channel_ids.channel_partner_ids = [
+ (3, self.env.ref('base.public_partner').id),
+ (4, partner.id),
+ ]
+ super(WebsiteVisitor, self)._link_to_partner(partner, update_values=update_values)
+
+ def _create_visitor(self):
+ visitor = super(WebsiteVisitor, self)._create_visitor()
+ mail_channel_uuid = json.loads(request.httprequest.cookies.get('im_livechat_session', '{}')).get('uuid')
+ if mail_channel_uuid:
+ mail_channel = request.env["mail.channel"].sudo().search([("uuid", "=", mail_channel_uuid)])
+ mail_channel.write({
+ 'livechat_visitor_id': visitor.id,
+ 'anonymous_name': visitor.display_name
+ })
+ return visitor