diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/website_livechat/models | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/website_livechat/models')
| -rw-r--r-- | addons/website_livechat/models/__init__.py | 9 | ||||
| -rw-r--r-- | addons/website_livechat/models/im_livechat.py | 19 | ||||
| -rw-r--r-- | addons/website_livechat/models/im_livechat_channel.py | 23 | ||||
| -rw-r--r-- | addons/website_livechat/models/ir_http.py | 13 | ||||
| -rw-r--r-- | addons/website_livechat/models/mail_channel.py | 76 | ||||
| -rw-r--r-- | addons/website_livechat/models/res_config_settings.py | 10 | ||||
| -rw-r--r-- | addons/website_livechat/models/website.py | 63 | ||||
| -rw-r--r-- | addons/website_livechat/models/website_visitor.py | 120 |
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 |
