# Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ from odoo.addons.mail.tools.discuss import Store from odoo.tools import email_normalize, html2plaintext, plaintext2html from markupsafe import Markup class DiscussChannel(models.Model): """ Chat Session Reprensenting a conversation between users. It extends the base method for anonymous usage. """ _name = 'discuss.channel' _inherit = ['rating.mixin', 'discuss.channel'] anonymous_name = fields.Char('Anonymous Name') channel_type = fields.Selection(selection_add=[('livechat', 'Livechat Conversation')], ondelete={'livechat': 'cascade'}) duration = fields.Float('Duration', compute='_compute_duration', help='Duration of the session in hours') livechat_active = fields.Boolean('Is livechat ongoing?', help='Livechat session is active until visitor leaves the conversation.') livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel', index='btree_not_null') livechat_operator_id = fields.Many2one('res.partner', string='Operator', index='btree_not_null') chatbot_current_step_id = fields.Many2one('chatbot.script.step', string='Chatbot Current Step') chatbot_message_ids = fields.One2many('chatbot.message', 'discuss_channel_id', string='Chatbot Messages') country_id = fields.Many2one('res.country', string="Country", help="Country of the visitor of the channel") _sql_constraints = [('livechat_operator_id', "CHECK((channel_type = 'livechat' and livechat_operator_id is not null) or (channel_type != 'livechat'))", 'Livechat Operator ID is required for a channel of type livechat.')] @api.depends('message_ids') def _compute_duration(self): for record in self: start = record.message_ids[-1].date if record.message_ids else record.create_date end = record.message_ids[0].date if record.message_ids else fields.Datetime.now() record.duration = (end - start).total_seconds() / 3600 def _to_store(self, store: Store): """Extends the channel header by adding the livechat operator and the 'anonymous' profile""" super()._to_store(store) chatbot_lang = self.env["chatbot.script"]._get_chatbot_language() for channel in self: channel_info = {} if channel.chatbot_current_step_id: # sudo: chatbot.script.step - returning the current script/step of the channel current_step_sudo = channel.chatbot_current_step_id.sudo().with_context(lang=chatbot_lang) chatbot_script = current_step_sudo.chatbot_script_id # sudo: channel - accessing chatbot messages to get the current step message step_message = next(( m.mail_message_id for m in channel.sudo().chatbot_message_ids if m.script_step_id == current_step_sudo and m.mail_message_id.author_id == chatbot_script.operator_partner_id ), None) if channel.chatbot_current_step_id.sudo().step_type != 'forward_operator' else None current_step = { 'scriptStep': current_step_sudo._format_for_frontend(), "message": Store.one_id(step_message), 'operatorFound': current_step_sudo.step_type == 'forward_operator' and len(channel.channel_member_ids) > 2, } channel_info["chatbot"] = { 'script': chatbot_script._format_for_frontend(), 'steps': [current_step], 'currentStep': current_step, } channel_info['anonymous_name'] = channel.anonymous_name channel_info['anonymous_country'] = { 'code': channel.country_id.code, 'id': channel.country_id.id, 'name': channel.country_id.name, } if channel.country_id else False if channel.channel_type == "livechat": channel_info["operator"] = Store.one( channel.livechat_operator_id, fields=["user_livechat_username", "write_date"] ) if channel.channel_type == "livechat" and self.env.user._is_internal(): channel_info["livechatChannel"] = Store.one( channel.livechat_channel_id, fields=["name"] ) store.add(channel, channel_info) @api.autovacuum def _gc_empty_livechat_sessions(self): hours = 1 # never remove empty session created within the last hour self.env.cr.execute(""" SELECT id as id FROM discuss_channel C WHERE NOT EXISTS ( SELECT 1 FROM mail_message M WHERE M.res_id = C.id AND m.model = 'discuss.channel' ) AND C.channel_type = 'livechat' AND livechat_channel_id IS NOT NULL AND COALESCE(write_date, create_date, (now() at time zone 'UTC'))::timestamp < ((now() at time zone 'UTC') - interval %s)""", ("%s hours" % hours,)) empty_channel_ids = [item['id'] for item in self.env.cr.dictfetchall()] self.browse(empty_channel_ids).unlink() def execute_command_history(self, **kwargs): self._bus_send("im_livechat.history_command", {"id": self.id}) def _get_visitor_leave_message(self, operator=False, cancel=False): return _('Visitor left the conversation.') def _close_livechat_session(self, **kwargs): """ Set deactivate the livechat channel and notify (the operator) the reason of closing the session.""" self.ensure_one() if self.livechat_active: member = self.channel_member_ids.filtered(lambda m: m.is_self) if member: member.fold_state = "closed" # sudo: discuss.channel.rtc.session - member of current user can leave call member.sudo()._rtc_leave_call() # sudo: discuss.channel - visitor left the conversation, state must be updated self.sudo().livechat_active = False # avoid useless notification if the channel is empty if not self.message_ids: return # Notify that the visitor has left the conversation # sudo: mail.message - posting visitor leave message is allowed self.sudo().message_post( author_id=self.env.ref('base.partner_root').id, body=Markup('
') % self._get_visitor_leave_message(**kwargs), message_type='notification', subtype_xmlid='mail.mt_comment' ) # Rating Mixin def _rating_get_parent_field_name(self): return 'livechat_channel_id' def _email_livechat_transcript(self, email): company = self.env.user.company_id render_context = { "company": company, "channel": self, } mail_body = self.env['ir.qweb']._render('im_livechat.livechat_email_template', render_context, minimal_qcontext=True) mail_body = self.env['mail.render.mixin']._replace_local_links(mail_body) mail = self.env['mail.mail'].sudo().create({ 'subject': _('Conversation with %s', self.livechat_operator_id.user_livechat_username or self.livechat_operator_id.name), 'email_from': company.catchall_formatted or company.email_formatted, 'author_id': self.env.user.partner_id.id, 'email_to': email, 'body_html': mail_body, }) mail.send() def _get_channel_history(self): """ Converting message body back to plaintext for correct data formatting in HTML field. """ return Markup('').join( Markup('%s: %s