[REF] im_livechat : refactoring, make it compatbile with the nw mail module, change reporting sql view (split them in 2), adapt view, ...

This commit is contained in:
Jérome Maes
2015-09-01 20:16:09 +02:00
committed by Aaron Bohy
parent 1d54858cbd
commit 95b97d1010
30 changed files with 531 additions and 299 deletions
-1
View File
@@ -53,7 +53,6 @@ class ImBus(models.Model):
"message": json_dump(message)
}
self.sudo().create(values)
self.env.cr.commit()
if random.random() < 0.01:
self.gc()
if channels:
+5 -1
View File
@@ -24,13 +24,17 @@ Help your customers with this chat, and analyse their feedback.
"views/mail_channel_views.xml",
"views/im_livechat_channel_views.xml",
"views/im_livechat_channel_templates.xml",
"report/im_livechat_report_views.xml",
"report/im_livechat_report_channel_views.xml",
"report/im_livechat_report_operator_views.xml",
"data/im_livechat_channel_data.xml"
],
'demo': [
"data/im_livechat_channel_demo.xml",
],
'depends': ["mail", "rating"],
'qweb': [
'static/src/xml/im_livechat_backend.xml',
],
'installable': True,
'auto_install': False,
'application': True,
+20 -20
View File
@@ -11,7 +11,8 @@ class LivechatController(http.Controller):
@http.route('/im_livechat/support/<string:dbname>/<int:channel_id>', type='http', auth='none')
def support_page(self, dbname, channel_id, **kwargs):
Channel = request.env['im_livechat.channel']
info = Channel.get_channel_infos(channel_id)
info = kwargs
info.update(Channel.get_channel_infos(channel_id))
info["dbname"] = dbname
info["channel"] = channel_id
info["channel_name"] = Channel.browse(channel_id).name
@@ -39,9 +40,9 @@ class LivechatController(http.Controller):
# don't return the initialization script, since its blocked (in the country)
return
rule_data = {
'action' : rule.action,
'auto_popup_timer' : rule.auto_popup_timer,
'regex_url' : rule.regex_url,
'action': rule.action,
'auto_popup_timer': rule.auto_popup_timer,
'regex_url': rule.regex_url,
}
info['rule'] = json.dumps(rule and rule_data or False)
return request.render('im_livechat.loader', info)
@@ -54,7 +55,7 @@ class LivechatController(http.Controller):
# if the user is identifiy (eg: portal user on the frontend), don't use the anonymous name. The user will be added to session.
if request.session.uid:
anonymous_name = False
return request.env['im_livechat.channel'].sudo(request.session.uid).get_channel_session(channel_id, anonymous_name)
return request.env['im_livechat.channel'].sudo(request.session.uid or SUPERUSER_ID).get_mail_channel(channel_id, anonymous_name)
@http.route('/im_livechat/available', type='json', auth="none")
def available(self, db, channel):
@@ -62,35 +63,34 @@ class LivechatController(http.Controller):
channel = request.env['im_livechat.channel'].sudo(user_id).browse(channel)
return len(channel.get_available_users()) > 0
@http.route('/rating/livechat/feedback', type='json', auth='none')
def rating_livechat_feedback(self, uuid, rate, reason=None, **kwargs):
Session = request.env['im_chat.session']
@http.route('/im_livechat/feedback', type='json', auth='none')
def feedback(self, uuid, rate, reason=None, **kwargs):
Channel = request.env['mail.channel']
Rating = request.env['rating.rating']
session_ids = Session.sudo().search([('uuid', '=', uuid)])
if session_ids:
session = session_ids[0]
channel = Channel.sudo().search([('uuid', '=', uuid)], limit=1)
print "RATING"
print channel
if channel:
# limit the creation : only ONE rating per session
values = {
'rating': rate,
}
if not session.rating_ids:
if not channel.rating_ids:
values.update({
'res_id': session.id,
'res_model': 'im_chat.session',
'res_id': channel.id,
'res_model': 'mail.channel',
'rating': rate,
'feedback': reason,
})
# find the partner related to the user of the conversation
rated_partner_id = False
if session.user_ids:
rated_partner_id = session.user_ids[0] and session.user_ids[0].partner_id.id or False
values['rated_partner_id'] = rated_partner_id
# find the partner (operator)
if channel.channel_partner_ids:
values['rated_partner_id'] = channel.channel_partner_ids[0] and channel.channel_partner_ids[0].id or False
# create the rating
rating = Rating.sudo().create(values)
else:
if reason:
values['feedback'] = reason
rating = session.rating_ids[0]
rating = channel.rating_ids[0]
rating.write(values)
return rating.id
return False
@@ -20,7 +20,7 @@
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="model">im_chat.session</field>
<field name="model">mail.channel</field>
<field name="function">cron_remove_empty_session</field>
<field name="active" eval="True"/>
</record>
@@ -28,47 +28,20 @@
</data>
<data>
<!-- Custom Filters -->
<record id="im_livechat_report_filter_session_by_day" model="ir.filters">
<field name="name">Sessions by day</field>
<field name="model_id">im_livechat.report</field>
<field name="domain">[('nbr_speakers','&gt;', 1)]</field>
<field name="context">{'group_by': ['start_date:day', 'session_name']}</field>
<field name="is_default">v</field>
<field name="user_id" eval="False"/>
</record>
<record id="im_livechat_report_filter_session_last_24h" model="ir.filters">
<field name="name">Last 24h stats</field>
<field name="model_id">im_livechat.report</field>
<field name="domain">[('start_date','&gt;', (context_today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d') )]</field>
<field name="context">{'group_by': ['start_date:day', 'session_name']}</field>
<field name="user_id" eval="False"/>
</record>
<record id="im_livechat_report_filter_operator_stats" model="ir.filters">
<field name="name">Operators stats</field>
<field name="model_id">im_livechat.report</field>
<field name="domain">[('nbr_speakers','&gt;', 1), ('user_id', '!=', False)]</field>
<field name="context">{'group_by': ['start_date:month', 'user_id', 'uuid']}</field>
<field name="user_id" eval="False"/>
</record>
<!-- Smiley for rating to be displayed in the conversation -->
<record id="im_chat_shortcode_rating_0" model="im_chat.shortcode">
<record id="mail_shortcode_rating_0" model="mail.shortcode">
<field name="source">:rating_0</field>
<field name="substitution">&lt;img src='/rating/static/src/img/rating_0.png'/&gt;</field>
<field name="description">Rating Smiley Bad</field>
</record>
<record id="im_chat_shortcode_rating_5" model="im_chat.shortcode">
<record id="mail_shortcode_rating_5" model="mail.shortcode">
<field name="source">:rating_5</field>
<field name="substitution">&lt;img src='/rating/static/src/img/rating_5.png'/&gt;</field>
<field name="description">Rating Smiley Okay</field>
</record>
<record id="im_chat_shorcode_rating_10" model="im_chat.shortcode">
<record id="mail_shorcode_rating_10" model="mail.shortcode">
<field name="source">:rating_10</field>
<field name="substitution">&lt;img src='/rating/static/src/img/rating_10.png'/&gt;</field>
<field name="description">Rating Smiley Good</field>
@@ -24,13 +24,12 @@ class ImLivechatChannel(models.Model):
def _default_user_ids(self):
return [(6, 0, [self._uid])]
# attribute fields
name = fields.Char('Name', required=True, help="The name of the channel")
button_text = fields.Char('Text of the Button', default='Have a Question? Chat with us.',
help="Default text displayed on the Livechat Support Button")
default_message = fields.Char('Welcome Message', default='How may I help you?',
help="This is an automated 'welcome' message that your visitor will see when they initiate a new chat session.")
help="This is an automated 'welcome' message that your visitor will see when they initiate a new conversation.")
input_placeholder = fields.Char('Chat Input Placeholder')
# computed fields
@@ -39,7 +38,7 @@ class ImLivechatChannel(models.Model):
are_you_inside = fields.Boolean(string='Are you inside the matrix?',
compute='_are_you_inside', store=False, readonly=True)
script_external = fields.Text('Script (external)', compute='_compute_script_external', store=False, readonly=True)
nbr_session = fields.Integer('Number of session', compute='_compute_nbr_session', store=False, readonly=True)
nbr_channel = fields.Integer('Number of conversation', compute='_compute_nbr_channel', store=False, readonly=True)
rating_percentage_satisfaction = fields.Integer('% Happy', compute='_compute_percentage_satisfaction', store=False, default=-1)
# images fields
@@ -56,7 +55,7 @@ class ImLivechatChannel(models.Model):
# relationnal fields
user_ids = fields.Many2many('res.users', 'im_livechat_channel_im_user', 'channel_id', 'user_id', string='Operators', default=_default_user_ids)
session_ids = fields.One2many('im_chat.session', 'channel_id', 'Sessions')
channel_ids = fields.One2many('mail.channel', 'livechat_channel_id', 'Sessions')
rule_ids = fields.One2many('im_livechat.channel.rule', 'channel_id', 'Rules')
@@ -92,16 +91,16 @@ class ImLivechatChannel(models.Model):
record.web_page = "%s/im_livechat/support/%s/%i" % (base_url, self._cr.dbname, record.id)
@api.multi
@api.depends('session_ids')
def _compute_nbr_session(self):
@api.depends('channel_ids')
def _compute_nbr_channel(self):
for record in self:
record.nbr_session = len(record.session_ids)
record.nbr_channel = len(record.channel_ids)
@api.multi
@api.depends('session_ids.rating_ids')
@api.depends('channel_ids.rating_ids')
def _compute_percentage_satisfaction(self):
for record in self:
repartition = record.session_ids.rating_get_grades()
repartition = record.channel_ids.rating_get_grades()
total = sum(repartition.values())
if total > 0:
happy = repartition['great']
@@ -109,7 +108,6 @@ class ImLivechatChannel(models.Model):
else:
record.rating_percentage_satisfaction = -1
# --------------------------
# Action Methods
# --------------------------
@@ -131,7 +129,7 @@ class ImLivechatChannel(models.Model):
"""
self.ensure_one()
action = self.env['ir.actions.act_window'].for_xml_id('rating', 'action_view_rating')
action['domain'] = [('res_id', 'in', [s.id for s in self.session_ids]), ('res_model', '=', 'im_chat.session')]
action['domain'] = [('res_id', 'in', [s.id for s in self.channel_ids]), ('res_model', '=', 'mail.channel')]
return action
# --------------------------
@@ -146,20 +144,36 @@ class ImLivechatChannel(models.Model):
return self.sudo().user_ids.filtered(lambda user: user.im_status == 'online')
@api.model
def get_channel_session(self, channel_id, anonymous_name):
""" return a session given a channel : create on with a registered user, or return false otherwise """
def get_mail_channel(self, livechat_channel_id, anonymous_name):
""" Return a mail.channel given a livechat channel. It creates one with a connected operator, or return false otherwise
:param livechat_channel_id : the identifier if the im_livechat.channel
:param anonymous_name : the name of the anonymous person of the channel
:type livechat_channel_id : int
:type anonymous_name : str
:return : channel header
:rtype : dict
"""
# get the avalable user of the channel
users = self.sudo().browse(channel_id).get_available_users()
users = self.sudo().browse(livechat_channel_id).get_available_users()
if len(users) == 0:
return False
user_id = random.choice(users).id
# user to add to the session
user_to_add = [(4, user_id)]
if self.env.uid:
user_to_add.append((4, self.env.uid))
# choose the res.users operator and get its partner id
user = random.choice(users)
operator_partner_id = user.partner_id.id
# partner to add to the mail.channel
channel_partner_to_add = [(4, operator_partner_id)]
if self.env.uid: # if the user if logged (portal user), he can be identify
channel_partner_to_add.append((4, self.env.user.partner_id.id))
# create the session, and add the link with the given channel
session = self.env["im_chat.session"].sudo().create({'user_ids': user_to_add, 'channel_id': channel_id, 'anonymous_name': anonymous_name})
return session.sudo().with_context(im_livechat_operator_id=user_id).session_info()
mail_channel = self.env["mail.channel"].with_context(mail_create_nosubscribe=False).sudo().create({
'channel_partner_ids': channel_partner_to_add,
'livechat_channel_id': livechat_channel_id,
'anonymous_name': anonymous_name,
'channel_type': 'livechat',
'name': ', '.join([anonymous_name, user.name]),
'public': 'public',
})
return mail_channel.sudo().with_context(im_livechat_operator_partner_id=operator_partner_id).channel_info()[0]
@api.model
def get_channel_infos(self, channel_id):
@@ -174,7 +188,6 @@ class ImLivechatChannel(models.Model):
}
class ImLivechatChannelRule(models.Model):
""" Channel Rules
Rules defining access to the channel (countries, and url matching). It also provide the 'auto pop'
@@ -202,7 +215,6 @@ class ImLivechatChannelRule(models.Model):
sequence = fields.Integer('Matching order', default=10,
help="Given the order to find a matching rule. If 2 rules are matching for the given url/country, the one with the lowest sequence will be chosen.")
def match_rule(self, channel_id, url, country_id=False):
""" determine if a rule of the given channel match with the given url
:param channel_id : the identifier of the channel_id
+46 -56
View File
@@ -3,79 +3,69 @@ from openerp import api, fields, models
from openerp import SUPERUSER_ID
class ImChatSession(models.Model):
class MailChannel(models.Model):
""" Chat Session
Reprensenting a conversation between users.
It extends the base method for anonymous usage.
"""
_name = 'im_chat.session'
_inherit = ['im_chat.session', 'rating.mixin']
_name = 'mail.channel'
_inherit = ['mail.channel', 'rating.mixin']
anonymous_name = fields.Char('Anonymous Name')
create_date = fields.Datetime('Create Date', required=True)
channel_id = fields.Many2one('im_livechat.channel', 'Channel')
fullname = fields.Char('Complete Name', compute='_compute_fullname')
channel_type = fields.Selection(selection_add=[('livechat', 'Livechat Conversation')])
livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel')
@api.multi
@api.depends('anonymous_name', 'user_ids')
def _compute_fullname(self):
""" built the complete name of the session """
for session in self:
names = []
for user in session.user_ids:
names.append(user.name)
if session.anonymous_name:
names.append(session.anonymous_name)
session.fullname = ', '.join(names)
@api.multi
def is_in_session(self):
""" Return True if the current user is in the user_ids of the session. False otherwise.
If this is executed as sudo, this might be the anonymous user.
def _channel_message_notifications(self, message):
""" When a anonymous user create a mail.channel, the operator is not notify (to avoid massive polling when
clicking on livechat button). So when the anonymous person is sending its FIRST message, the channel header
should be added to the notification, since the user cannot be listining to the channel.
"""
self.ensure_one()
if self.anonymous_name and self._uid == SUPERUSER_ID:
return True
else:
return super(ImChatSession, self).is_in_session()
notifications = super(MailChannel, self)._channel_message_notifications(message)
if not message.author_id:
unpinned_channel_partner = self.mapped('channel_last_seen_partner_ids').filtered(lambda cp: not cp.is_pinned)
if unpinned_channel_partner:
unpinned_channel_partner.write({'is_pinned': True})
notifications = self._channel_channel_notifications(unpinned_channel_partner.mapped('partner_id').ids) + notifications
return notifications
@api.multi
def session_user_info(self):
""" Get the user infos for all the identified user in the session + the anonymous if anonymous session
:returns a list of user infos
def channel_info(self):
""" Extends the channel header by adding the livechat operator and the 'anonymous' profile
:rtype : list(dict)
"""
self.ensure_one()
users_infos = super(ImChatSession, self).session_user_info()
# identify the operator for the 'welcome message'
for user_profile in users_infos:
user_profile['is_operator'] = bool(user_profile['id'] == self.env.context.get('im_livechat_operator_id'))
if self.anonymous_name:
users_infos.append({'id': False, 'name': self.anonymous_name, 'im_status': 'online', 'is_operator': False})
return users_infos
channel_infos = super(MailChannel, self).channel_info()
# add the operator id
if self.env.context.get('im_livechat_operator_partner_id'):
partner_name = self.env['res.partner'].browse(self.env.context.get('im_livechat_operator_partner_id')).name_get()[0]
for channel_info in channel_infos:
channel_info['operator_pid'] = partner_name
# add the anonymous name
channel_infos_dict = dict((c['id'], c) for c in channel_infos)
for channel in self:
if channel.anonymous_name:
channel_infos_dict[channel.id]['anonymous_name'] = channel.anonymous_name
return channel_infos_dict.values()
@api.model
def quit_user(self, uuid):
""" Remove the current user from the given session.
Note : an anonymous user cannot leave the session, since he is not registered.
Required to modify the base comportement, since a session can contain only 1 identified user.
:param uuid : the uuid of the session to quit
"""
session = self.search([('uuid', '=', uuid)], limit=1)
if session.anonymous_name:
# an identified user can leave an anonymous session if there is still another idenfied user in it
if self._uid and self._uid in [u.id for u in session.user_ids] and len(session.user_ids) > 1:
self._remove_user()
return True
return False
else:
return super(ImChatSession, self).quit_user(uuid)
def channel_fetch_slot(self):
values = super(MailChannel, self).channel_fetch_slot()
pinned_channels = self.env['mail.channel.partner'].search([('partner_id', '=', self.env.user.partner_id.id), ('is_pinned', '=', True)]).mapped('channel_id')
values['channel_livechat'] = self.search([('channel_type', '=', 'livechat'), ('public', 'in', ['public']), ('id', 'in', pinned_channels.ids)]).channel_info()
return values
@api.model
def cron_remove_empty_session(self):
groups = self.env['im_chat.message'].read_group([], ['to_id'], ['to_id'])
not_empty_session_ids = [group['to_id'][0] for group in groups]
empty_sessions = self.search([('id', 'not in', not_empty_session_ids), ('channel_id', '!=', False)])
empty_sessions.unlink()
self.env.cr.execute("""
SELECT id as id
FROM mail_channel C
WHERE NOT EXISTS (
SELECT *
FROM mail_message_mail_channel_rel R
WHERE R.mail_channel_id = C.id
) AND C.channel_type = 'livechat' AND livechat_channel_id IS NOT NULL;
""")
empty_channel_ids = [item['id'] for item in self.env.cr.dictfetchall()]
self.browse(empty_channel_ids).unlink()
+2 -3
View File
@@ -11,9 +11,8 @@ class Rating(models.Model):
def _compute_res_name(self):
# cannot change the rec_name of session since it is use to create the bus channel
# so, need to override this method to set the same alternative rec_name as in reporting
if self.res_model == 'im_chat.session':
if self.res_model == 'mail.channel':
current_object = self.env[self.res_model].sudo().browse(self.res_id)
self.res_name = ('%s / %s') % (current_object.channel_id.name, current_object.id)
self.res_name = ('%s / %s') % (current_object.livechat_channel_id.name, current_object.id)
else:
super(Rating, self)._compute_res_name()
+2 -1
View File
@@ -1,2 +1,3 @@
# -*- coding: utf-8 -*-
import im_livechat_report
import im_livechat_report_channel
import im_livechat_report_operator
@@ -1,55 +1,51 @@
# -*- coding: utf-8 -*-
from openerp import api, fields, models, tools
from openerp import fields, models, tools
class im_livechat_report(models.Model):
""" Livechat Support Report """
class ImLivechatReportChannel(models.Model):
""" Livechat Support Report on the Channels """
_name = "im_livechat.report"
_name = "im_livechat.report.channel"
_description = "Livechat Support Report"
_order = 'start_date, session_name'
_order = 'start_date, technical_name'
_auto = False
session_name = fields.Char('Session Name', readonly=True)
uuid = fields.Char('UUID', readonly=True)
channel_id = fields.Many2one('mail.channel', 'Conversation', readonly=True)
channel_name = fields.Char('Channel Name', readonly=True)
technical_name = fields.Char('Code', readonly=True)
livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel', readonly=True)
start_date = fields.Datetime('Start Date of session', readonly=True, help="Start date of the conversation")
start_date_hour = fields.Char('Hour of start Date of session', readonly=True)
duration = fields.Float('Average duration', digits=(16, 2), readonly=True, group_operator="avg", help="Duration of the conversation (in seconds)")
time_in_session = fields.Float('Time in session', digits=(16, 2), readonly=True, group_operator="avg", help="Average time the user spend in the conversation")
time_to_answer = fields.Float('Time to answer', digits=(16, 2), readonly=True, group_operator="avg", help="Average time to give the first answer to the visitor")
nbr_messages = fields.Integer('Average message', readonly=True, group_operator="avg", help="Number of message in the conversation")
nbr_user_messages = fields.Integer('Average of messages/user', readonly=True, group_operator="avg", help="Average number of message per user")
nbr_speakers = fields.Integer('# of speakers', readonly=True, group_operator="avg", help="Number of different speakers")
user_id = fields.Many2one('res.users', 'User', readonly=True)
session_id = fields.Many2one('im_chat.session', 'Session', readonly=True)
channel_id = fields.Many2one('im_livechat.channel', 'Channel', readonly=True)
nbr_speaker = fields.Integer('# of speakers', readonly=True, group_operator="avg", help="Number of different speakers")
nbr_message = fields.Integer('Average message', readonly=True, group_operator="avg", help="Number of message in the conversation")
partner_id = fields.Many2one('res.partner', 'Opertor', readonly=True)
def init(self, cr):
# Note : start_date_hour must be remove when the read_group will allow grouping on the hour of a datetime. Don't forget to change the view !
tools.drop_view_if_exists(cr, 'im_livechat_report')
tools.drop_view_if_exists(cr, 'im_livechat_report_channel')
cr.execute("""
CREATE OR REPLACE VIEW im_livechat_report AS (
CREATE OR REPLACE VIEW im_livechat_report_channel AS (
SELECT
min(M.id) as id,
S.uuid as uuid,
CONCAT(C.name, ' / ', S.id) as session_name,
S.create_date as start_date,
to_char(date_trunc('hour', S.create_date), 'YYYY-MM-DD HH24:MI:SS') as start_date_hour,
EXTRACT('epoch' from ((SELECT (max(create_date)-min(create_date)) FROM im_chat_message WHERE to_id=S.id AND from_id = U.id))) as time_in_session,
EXTRACT('epoch' from ((SELECT min(create_date) FROM im_chat_message WHERE to_id=S.id AND from_id IS NOT NULL)-(SELECT min(create_date) FROM im_chat_message WHERE to_id=S.id AND from_id IS NULL))) as time_to_answer,
EXTRACT('epoch' from (max((SELECT (max(create_date)) FROM im_chat_message WHERE to_id=S.id))-S.create_date)) as duration,
(SELECT count(distinct COALESCE(from_id, 0)) FROM im_chat_message WHERE to_id=S.id) as nbr_speakers,
(SELECT count(id) FROM im_chat_message WHERE to_id=S.id) as nbr_messages,
count(M.id) as nbr_user_messages,
U.id as user_id,
S.channel_id as channel_id,
S.id as session_id
FROM im_chat_message M
LEFT JOIN im_chat_session S on (S.id = M.to_id)
LEFT JOIN res_users U on (U.id = M.from_id)
LEFT JOIN im_livechat_channel C on (S.channel_id = C.id)
WHERE S.channel_id IS NOT NULL
GROUP BY U.id, M.to_id, S.id, C.name, S.uuid
C.id as id,
C.uuid as uuid,
C.id as channel_id,
C.name as channel_name,
CONCAT(L.name, ' / ', C.id) as technical_name,
C.livechat_channel_id as livechat_channel_id,
C.create_date as start_date,
to_char(date_trunc('hour', C.create_date), 'YYYY-MM-DD HH24:MI:SS') as start_date_hour,
EXTRACT('epoch' FROM (max((SELECT (max(M.create_date)) FROM mail_message M JOIN mail_message_mail_channel_rel R ON (R.mail_message_id = M.id) WHERE R.mail_channel_id = C.id))-C.create_date)) as duration,
count(distinct P.id) as nbr_speaker,
count(distinct M.id) as nbr_message,
MAX(S.partner_id) as partner_id
FROM mail_channel C
JOIN mail_message_mail_channel_rel R ON (C.id = R.mail_channel_id)
JOIN mail_message M ON (M.id = R.mail_message_id)
JOIN mail_channel_partner S ON (S.channel_id = C.id)
JOIN im_livechat_channel L ON (L.id = C.livechat_channel_id)
LEFT JOIN res_partner P ON (M.author_id = P.id)
GROUP BY C.id, C.name, C.livechat_channel_id, L.name, C.create_date, C.uuid
)
""")
@@ -2,42 +2,42 @@
<odoo>
<data>
<record id="im_livechat_report_view_pivot" model="ir.ui.view">
<field name="name">im_livechat.report.pivot</field>
<field name="model">im_livechat.report</field>
<record id="im_livechat_report_channel_view_pivot" model="ir.ui.view">
<field name="name">im_livechat.report.channel.pivot</field>
<field name="model">im_livechat.report.channel</field>
<field name="arch" type="xml">
<pivot string="Livechat Support Statistics" disable_linking="True">
<field name="session_name" type="row"/>
<field name="technical_name" type="row"/>
<field name="duration" type="measure"/>
<field name="nbr_messages" type="measure"/>
<field name="time_to_answer" type="measure"/>
<field name="nbr_message" type="measure"/>
</pivot>
</field>
</record>
<record id="im_livechat_report_view_graph" model="ir.ui.view">
<field name="name">im_livechat.report.graph</field>
<field name="model">im_livechat.report</field>
<record id="im_livechat_report_channel_view_graph" model="ir.ui.view">
<field name="name">im_livechat.report.channel.graph</field>
<field name="model">im_livechat.report.channel</field>
<field name="arch" type="xml">
<graph string="Livechat Support Statistics">
<field name="session_name"/>
<field name="nbr_messages" type="measure"/>
<field name="technical_name"/>
<field name="nbr_message" type="measure"/>
</graph>
</field>
</record>
<record id="im_livechat_report_view_search" model="ir.ui.view">
<field name="name">im_livechat.report.search</field>
<field name="model">im_livechat.report</field>
<record id="im_livechat_report_channel_view_search" model="ir.ui.view">
<field name="name">im_livechat.report.channel.search</field>
<field name="model">im_livechat.report.channel</field>
<field name="arch" type="xml">
<search string="Search report">
<filter name="missed_session" string="Missed sessions" domain="[('nbr_speakers','&lt;=', 1)]"/>
<filter name="treated_session" string="Treated sessions" domain="[('nbr_speakers','&gt;', 1)]"/>
<filter name="missed_session" string="Missed sessions" domain="[('nbr_speaker','&lt;=', 1)]"/>
<filter name="treated_session" string="Treated sessions" domain="[('nbr_speaker','&gt;', 1)]"/>
<filter name="last_24h" string="Last 24h" domain="[('start_date','&gt;', (context_today() - datetime.timedelta(days=1)).strftime('%%Y-%%m-%%d') )]"/>
<filter name="last_week" string="Last Week" domain="[('start_date','&gt;', (context_today() - datetime.timedelta(weeks=1)).strftime('%%Y-%%m-%%d') )]"/>
<group expand="0" string="Group By...">
<filter name="group_by_session" string="Session" domain="[]" context="{'group_by':'session_name'}"/>
<filter name="group_by_channel" string="Channel" domain="[]" context="{'group_by':'channel_id'}"/>
<filter name="group_by_user" string="Operator" domain="[('user_id','!=', False)]" context="{'group_by':'user_id'}"/>
<filter name="group_by_operator" string="Operator" domain="[('partner_id','!=', False)]" context="{'group_by':'partner_id'}"/>
<separator orientation="vertical" />
<filter name="group_by_hour" string="Creation date (hour)" domain="[]" context="{'group_by':'start_date_hour'}"/>
<filter name="group_by_day" string="Creation date (day)" domain="[]" context="{'group_by':'start_date:day'}"/>
@@ -49,22 +49,29 @@
</field>
</record>
<record id="im_livechat_report_action" model="ir.actions.act_window">
<field name="name">Livechat Support Report</field>
<field name="res_model">im_livechat.report</field>
<record id="im_livechat_report_channel_action" model="ir.actions.act_window">
<field name="name">Livechat Support Report Channel</field>
<field name="res_model">im_livechat.report.channel</field>
<field name="view_type">form</field>
<field name="view_mode">pivot,graph</field>
<field name="help">Livechat Support Analysis allows you to easily check and analyse your company livechat session performance. Extract informations about the missed sessions, the audiance, the duration of a session, etc.</field>
<field name="context">{"search_default_last_week":1}</field>
<field name="help">Livechat Support Channel Statistics allows you to easily check and analyse your company livechat session performance. Extract informations about the missed sessions, the audiance, the duration of a session, etc.</field>
</record>
<menuitem
id="menu_reporting_livechat"
name="Report"
parent="menu_livechat_root"
sequence="50"
groups="im_livechat_group_manager"
action="im_livechat_report_action"/>
groups="im_livechat_group_manager"/>
<menuitem
id="menu_reporting_livechat_channel"
name="Session Statistics"
parent="menu_reporting_livechat"
sequence="10"
action="im_livechat_report_channel_action"/>
</data>
</odoo>
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
from openerp import fields, models, tools
class ImLivechatReportOperator(models.Model):
""" Livechat Support Report on the Operator """
_name = "im_livechat.report.operator"
_description = "Livechat Support Report"
_order = 'livechat_channel_id, partner_id'
_auto = False
partner_id = fields.Many2one('res.partner', 'Operator', readonly=True)
livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel', readonly=True)
nbr_channel = fields.Integer('# of channel', readonly=True, group_operator="sum", help="Number of conversation")
channel_id = fields.Many2one('mail.channel', 'Conversation', readonly=True)
start_date = fields.Datetime('Start Date of session', readonly=True, help="Start date of the conversation")
time_to_answer = fields.Float('Time to answer', digits=(16, 2), readonly=True, group_operator="avg", help="Average time to give the first answer to the visitor")
duration = fields.Float('Average duration', digits=(16, 2), readonly=True, group_operator="avg", help="Duration of the conversation (in seconds)")
def init(self, cr):
# Note : start_date_hour must be remove when the read_group will allow grouping on the hour of a datetime. Don't forget to change the view !
tools.drop_view_if_exists(cr, 'im_livechat_report_operator')
cr.execute("""
CREATE OR REPLACE VIEW im_livechat_report_operator AS (
SELECT
row_number() OVER () AS id,
P.id as partner_id,
L.id as livechat_channel_id,
count(C.id) as nbr_channel,
C.id as channel_id,
C.create_date as start_date,
EXTRACT('epoch' FROM (max((SELECT (max(M.create_date)) FROM mail_message M JOIN mail_message_mail_channel_rel R ON (R.mail_message_id = M.id) WHERE R.mail_channel_id = C.id))-C.create_date)) as duration,
EXTRACT('epoch' from ((SELECT min(M.create_date) FROM mail_message M, mail_message_mail_channel_rel R WHERE M.author_id=P.id AND R.mail_channel_id = C.id AND R.mail_message_id = M.id)-(SELECT min(M.create_date) FROM mail_message M, mail_message_mail_channel_rel R WHERE M.author_id IS NULL AND R.mail_channel_id = C.id AND R.mail_message_id = M.id))) as time_to_answer
FROM im_livechat_channel_im_user O
JOIN res_users U ON (O.user_id = U.id)
JOIN res_partner P ON (U.partner_id = P.id)
LEFT JOIN im_livechat_channel L ON (L.id = O.channel_id)
LEFT JOIN mail_channel C ON (C.livechat_channel_id = L.id)
GROUP BY P.id, L.id, C.id, C.create_date
)
""")
@@ -0,0 +1,66 @@
<?xml version="1.0"?>
<odoo>
<data>
<record id="im_livechat_report_operator_view_pivot" model="ir.ui.view">
<field name="name">im_livechat.report.operator.pivot</field>
<field name="model">im_livechat.report.operator</field>
<field name="arch" type="xml">
<pivot string="Livechat Support Statistics" disable_linking="True">
<field name="partner_id" type="row"/>
<field name="duration" type="measure"/>
<field name="nbr_channel" type="measure"/>
</pivot>
</field>
</record>
<record id="im_livechat_report_operator_view_graph" model="ir.ui.view">
<field name="name">im_livechat.report.operator.graph</field>
<field name="model">im_livechat.report.operator</field>
<field name="arch" type="xml">
<graph string="Livechat Support Statistics">
<field name="partner_id"/>
<field name="nbr_channel" type="measure"/>
</graph>
</field>
</record>
<record id="im_livechat_report_operator_view_search" model="ir.ui.view">
<field name="name">im_livechat.report.operator.search</field>
<field name="model">im_livechat.report.operator</field>
<field name="arch" type="xml">
<search string="Search report">
<filter name="last_24h" string="Last 24h" domain="[('start_date','&gt;', (context_today() - datetime.timedelta(days=1)).strftime('%%Y-%%m-%%d') )]"/>
<filter name="last_week" string="Last Week" domain="[('start_date','&gt;', (context_today() - datetime.timedelta(weeks=1)).strftime('%%Y-%%m-%%d') )]"/>
<group expand="0" string="Group By...">
<filter name="group_by_channel" string="Channel" domain="[]" context="{'group_by':'channel_id'}"/>
<filter name="group_by_operator" string="Operator" domain="[('partner_id','!=', False)]" context="{'group_by':'partner_id'}"/>
<separator orientation="vertical" />
<filter name="group_by_hour" string="Creation date (hour)" domain="[]" context="{'group_by':'start_date_hour'}"/>
<filter name="group_by_day" string="Creation date (day)" domain="[]" context="{'group_by':'start_date:day'}"/>
<filter name="group_by_week" string="Creation date (week)" domain="[]" context="{'group_by':'start_date:week'}"/>
<filter name="group_by_month" string="Creation date (month)" domain="[]" context="{'group_by':'start_date:month'}" />
<filter name="group_by_year" string="Creation date (year)" domain="[]" context="{'group_by':'start_date:year'}"/>
</group>
</search>
</field>
</record>
<record id="im_livechat_report_operator_action" model="ir.actions.act_window">
<field name="name">Livechat Support Report Channel</field>
<field name="res_model">im_livechat.report.operator</field>
<field name="view_type">form</field>
<field name="view_mode">pivot,graph</field>
<field name="context">{"search_default_last_week":1}</field>
<field name="help">Livechat Support Channel Statistics allows you to easily check and analyse your company livechat session performance. Extract informations about the missed sessions, the audiance, the duration of a session, etc.</field>
</record>
<menuitem
id="menu_reporting_livechat_operator"
name="Operator Analysis"
parent="menu_reporting_livechat"
sequence="10"
action="im_livechat_report_operator_action"/>
</data>
</odoo>
@@ -21,22 +21,22 @@
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
<record id="im_chat_message_rule_manager" model="ir.rule">
<record id="mail_message_rule_manager" model="ir.rule">
<field name="name">Live Support Managers can read messages from live support</field>
<field name="model_id" ref="im_chat.model_im_chat_message"/>
<field name="model_id" ref="mail.model_mail_message"/>
<field name="groups" eval="[(6,0,[ref('im_livechat.im_livechat_group_manager')])]"/>
<field name="domain_force">[('to_id.channel_id', '!=', None)]</field>
<field name="domain_force">[('mail.channel_ids.livechat_channel_id', '!=', None)]</field>
<field name="perm_unlink" eval="0"/>
<field name="perm_write" eval="0"/>
<field name="perm_read" eval="1"/>
<field name="perm_create" eval="0"/>
</record>
<record id="im_chat_session_rule_manager" model="ir.rule">
<record id="mail_channel_rule_manager" model="ir.rule">
<field name="name">Live Support Managers can see all session from live support</field>
<field name="model_id" ref="im_chat.model_im_chat_session"/>
<field name="model_id" ref="mail.model_mail_channel"/>
<field name="groups" eval="[(6,0,[ref('im_livechat.im_livechat_group_manager')])]"/>
<field name="domain_force">[('channel_id', '!=', None)]</field>
<field name="domain_force">[('mail.channel_ids.livechat_channel_id', '!=', None)]</field>
<field name="perm_unlink" eval="1"/>
<field name="perm_write" eval="0"/>
<field name="perm_read" eval="1"/>
@@ -2,7 +2,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_livechat_channel,im_livechat.channel,model_im_livechat_channel,,1,0,0,0
access_livechat_channel_user,im_livechat.channel,model_im_livechat_channel,im_livechat_group_user,1,1,1,0
access_livechat_channel_manager,im_livechat.channel,model_im_livechat_channel,im_livechat_group_manager,1,1,1,1
access_livechat_support_report,im_livechat.report,model_im_livechat_report,im_livechat_group_manager,1,1,1,1
access_livechat_support_report_channel,im_livechat.report.channel,model_im_livechat_report_channel,im_livechat_group_manager,1,1,1,1
access_livechat_support_report_operator,im_livechat.report.operator,model_im_livechat_report_operator,im_livechat_group_manager,1,1,1,1
access_livechat_channel_rule,im_livechat.channel.rule,model_im_livechat_channel_rule,,1,0,0,0
access_livechat_channel_rule_user,im_livechat.channel.rule,model_im_livechat_channel_rule,im_livechat_group_user,1,1,1,0
access_livechat_channel_rule_manager,im_livechat.channel.rule,model_im_livechat_channel_rule,im_livechat_group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_livechat_channel im_livechat.channel model_im_livechat_channel 1 0 0 0
3 access_livechat_channel_user im_livechat.channel model_im_livechat_channel im_livechat_group_user 1 1 1 0
4 access_livechat_channel_manager im_livechat.channel model_im_livechat_channel im_livechat_group_manager 1 1 1 1
5 access_livechat_support_report access_livechat_support_report_channel im_livechat.report im_livechat.report.channel model_im_livechat_report model_im_livechat_report_channel im_livechat_group_manager 1 1 1 1
6 access_livechat_support_report_operator im_livechat.report.operator model_im_livechat_report_operator im_livechat_group_manager 1 1 1 1
7 access_livechat_channel_rule im_livechat.channel.rule model_im_livechat_channel_rule 1 0 0 0
8 access_livechat_channel_rule_user im_livechat.channel.rule model_im_livechat_channel_rule im_livechat_group_user 1 1 1 0
9 access_livechat_channel_rule_manager im_livechat.channel.rule model_im_livechat_channel_rule im_livechat_group_user 1 1 1 1
@@ -1,42 +1,75 @@
.openerp { /* base style of openerp */
font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
color: #4c4c4c;
font-size: 13px;
}
.o_mail_chat_im_window * {
box-sizing: border-box;
}
.oe_chat_button {
width: 230px;
position: fixed;
bottom: 0px;
right: 6px;
display: inline-block;
min-width: 100px;
background-color: rgba(60, 60, 60, 0.6);
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Arial, Verdana, sans-serif;
font-size: 14px;
font-weight: bold;
padding: 10px;
color: white;
text-shadow: rgb(59, 76, 88) 1px 1px 0px;
border: 1px solid rgb(80, 80, 80);
border-bottom: 0px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
cursor: pointer;
z-index:500;
}
/* Livechat Rating : feedback smiley */
.oe_im_chatview .oe_livechat_rating{
.o_mail_chat_im_window_content .o_livechat_rating{
padding: 15px;
vertical-align: middle;
}
.oe_im_chatview .oe_livechat_rating .oe_im_feedback_text {
color: white;
.o_mail_chat_im_window_content .o_livechat_rating .o_livechat_rating_feedback_text {
vertical-align: middle;
font-size: 14px;
}
.oe_im_chatview .oe_livechat_rating .oe_livechat_rating_choices{
.o_mail_chat_im_window_content .o_livechat_rating .o_livechat_rating_choices{
height:40px;
text-align: center;
margin: 10px 0 30px 0;
}
.oe_im_chatview .oe_livechat_rating .oe_livechat_rating_choices img{
.o_mail_chat_im_window_content .o_livechat_rating .o_livechat_rating_choices img{
width: 50px;
opacity: 0.60;
cursor: pointer;
margin: 5px;
}
.oe_im_chatview .oe_livechat_rating .oe_livechat_rating_choices img:hover{
.o_mail_chat_im_window_content .o_livechat_rating .o_livechat_rating_choices img:hover{
opacity:1;
}
.oe_im_chatview .oe_livechat_rating .oe_livechat_rating_choices img.selected{
.o_mail_chat_im_window_content .o_livechat_rating .o_livechat_rating_choices img.selected{
opacity: 1;
}
/* feedback reason */
.oe_im_chatview .oe_livechat_rating .oe_livechat_rating_reason {
.o_mail_chat_im_window_content .o_livechat_rating .o_livechat_rating_reason {
margin: 10px 0;
display: none; /* hidden by default */
}
.oe_im_chatview .oe_livechat_rating .oe_livechat_rating_reason textarea {
.o_mail_chat_im_window_content .o_livechat_rating .o_livechat_rating_reason textarea {
width: 100%;
height: 70px;
resize: none;
}
.oe_im_chatview .oe_livechat_rating .oe_livechat_rating_reason_button input{
.o_mail_chat_im_window_content .o_livechat_rating .o_livechat_rating_reason_button input{
float: right;
margin-left : 10px;
}
+33 -32
View File
@@ -7,7 +7,7 @@ var user_session = require('web.session');
var time = require('web.time');
var utils = require('web.utils');
var Widget = require('web.Widget');
var im_chat_common = require('mail.chat_common');
var mail_chat_common = require('mail.chat_common');
var _t = core._t;
var QWeb = core.qweb;
@@ -19,11 +19,12 @@ var COOKIE_NAME = 'livechat_conversation';
The state of anonymous session is hold by the client and not the server.
Override the method managing the state of normal conversation.
*/
im_chat_common.Conversation.include({
init: function(){
mail_chat_common.Conversation.include({
init: function(parent, session, options){
this._super.apply(this, arguments);
this.shown = true;
this.feedback = false;
this.options.default_username = session.anonymous_name || this.options.default_username;
},
show: function(){
this._super.apply(this, arguments);
@@ -33,7 +34,7 @@ im_chat_common.Conversation.include({
this._super.apply(this, arguments);
this.shown = false;
},
session_update_state: function(state){
session_fold: function(state){
// set manually the new state
if(state === 'closed'){
this.destroy();
@@ -54,14 +55,14 @@ im_chat_common.Conversation.include({
this._super(state);
utils.set_cookie(COOKIE_NAME, JSON.stringify(this.get('session')), 60*60);
},
click_close: function(event) {
on_click_close: function(event) {
if(!this.feedback && (this.get('messages').length > 1)){
this.feedback = new Feedback(this);
this.$(".oe_im_chatview_content").empty();
this.$(".oe_im_chatview_input").prop('disabled', true);
this.feedback.appendTo( this.$(".oe_im_chatview_content"));
this.$(".o_mail_chat_im_window_content").empty();
this.$(".o_mail_chat_im_window_input").prop('disabled', true);
this.feedback.appendTo(this.$(".o_mail_chat_im_window_content"));
// bind event to close conversation
this.feedback.on("feedback_sent", this, this.click_close);
this.feedback.on("feedback_sent", this, this.on_click_close);
}else{
this._super.apply(this, arguments);
utils.set_cookie(COOKIE_NAME, "", -1);
@@ -72,7 +73,7 @@ im_chat_common.Conversation.include({
// To avoid exeption when the anonymous has close his
// conversation and when operator send him a message.
im_chat_common.ConversationManager.include({
var LivechatConversationManager = mail_chat_common.ConversationManager.extend({
message_receive: function(message) {
try{
this._super(message);
@@ -95,7 +96,7 @@ var LiveSupport = Widget.extend({
this.load_template(db, channel, options, rule);
},
_get_template_list: function(){
return ['/im_livechat/static/src/xml/im_livechat.xml', '/im_chat/static/src/xml/im_chat.xml'];
return ['/im_livechat/static/src/xml/im_livechat.xml', '/mail/static/src/xml/mail_chat_common.xml'];
},
load_template: function(db, channel, options, rule){
var self = this;
@@ -153,7 +154,7 @@ var ChatButton = Widget.extend({
start: function() {
this.$().append(QWeb.render("im_livechat.chatButton", {widget: this}));
// set up the manager
this.manager = new im_chat_common.ConversationManager(this, this.options);
this.manager = new LivechatConversationManager(this, this.options);
this.manager.set("bottom_offset", $('.oe_chat_button').outerHeight());
if(this.session){
this.set_conversation(this.session);
@@ -200,20 +201,20 @@ var ChatButton = Widget.extend({
},
send_welcome_message: function(){
var self = this;
if(this.session.users.length > 0){
if (self.options.defaultMessage) {
setTimeout(function(){
var operator = _.last(_.filter(self.session.users, function(user){return user.is_operator}));
self.conv.message_receive({
id : 1,
type: "message",
message: self.options.defaultMessage,
create_date: time.datetime_to_str(new Date()),
from_id: [operator.id, operator.name],
to_id: [0, self.session.uuid]
});
}, 1000);
}
if(this.session.operator_pid && self.options.defaultMessage) {
setTimeout(function(){
self.conv.message_receive({
id : 1,
message_type: "comment",
model: 'mail.channel',
body: self.options.defaultMessage,
date: time.datetime_to_str(new Date()),
author_id: self.session.operator_pid,
channel_ids: [self.session.id],
tracking_value_ids: [],
attachment_ids: [],
});
}, 1000);
}
}
});
@@ -231,20 +232,20 @@ var Feedback = Widget.extend({
start: function(){
this._super.apply(this.arguments);
// bind events
this.$('.oe_livechat_rating_choices img').on('click', _.bind(this.click_smiley, this));
this.$('.o_livechat_rating_choices img').on('click', _.bind(this.click_smiley, this));
this.$('#rating_submit').on('click', _.bind(this.click_send, this));
},
click_smiley: function(ev){
var self = this;
this.rating = parseInt($(ev.currentTarget).data('value'));
this.$('.oe_livechat_rating_choices img').removeClass('selected');
this.$('.oe_livechat_rating_choices img[data-value="'+this.rating+'"]').addClass('selected');
this.$('.o_livechat_rating_choices img').removeClass('selected');
this.$('.o_livechat_rating_choices img[data-value="'+this.rating+'"]').addClass('selected');
// only display textearea if bad smiley selected
var close_conv = false;
if(this.rating === 0){
this.$('.oe_livechat_rating_reason').show();
this.$('.o_livechat_rating_reason').show();
}else{
this.$('.oe_livechat_rating_reason').hide();
this.$('.o_livechat_rating_reason').hide();
close_conv = true;
}
this._send_feedback(close_conv).then(function(){
@@ -260,7 +261,7 @@ var Feedback = Widget.extend({
_send_feedback: function(close){
var self = this;
var uuid = this.conversation.get('session').uuid;
return user_session.rpc('/rating/livechat/feedback', {uuid: uuid, rate: this.rating, reason : this.reason}).then(function(res) {
return user_session.rpc('/im_livechat/feedback', {uuid: uuid, rate: this.rating, reason : this.reason}).then(function(res) {
if(close){
self.trigger("feedback_sent"); // will close the conversation
self.conversation.message_send(_.str.sprintf(_t("I rated you with :rating_%d"), self.rating), "message");
@@ -0,0 +1,53 @@
odoo.define('im_livechat.chat_backend', function (require) {
"use strict";
var mail_chat_backend = require('mail.chat_backend');
/**
* Patch for the client action to integrate Livechat Mail Channel in a particular slot
*/
mail_chat_backend.ChatMailThread.include({
init: function(parent, action){
this._super.apply(this, arguments);
this.set('channel_livechat', []);
},
start: function(){
var self = this;
this.on("change:channel_livechat", this, function(){
self.channel_render('channel_livechat');
});
return this._super.apply(this, arguments).then(function(){
self.$el.on('click', '.o_mail_chat_channel_unpin', _.bind(self.on_click_channel_unpin, self));
});
},
on_click_channel_unpin: function(event){
var self = this;
var channel_id = this.$(event.currentTarget).data('channel-id');
var channel = this.channels[channel_id];
return this.channel_pin(channel.uuid, false).then(function(){
self.channel_remove(channel_id);
// if unpin current channel, switch to inbox
if(self.get('current_channel_id') === channel_id){
self.set('current_channel_id', 'channel_inbox');
}
});
},
channel_change: function(){
// update the default username
var current_channel = this.channels[this.get('current_channel_id')];
if(current_channel){
this.options.default_username = current_channel.anonymous_name || this.options.default_username
}
return this._super.apply(this, arguments);
},
// utils function
get_channel_slot: function(channel){
if(channel.channel_type === 'livechat'){
return 'channel_livechat';
}
return this._super.apply(this, arguments);
},
});
});
@@ -7,18 +7,18 @@
</t>
<t t-name="im_livechat.feedback">
<div class="oe_livechat_rating">
<div class="oe_im_feedback_text">
<div class="o_livechat_rating">
<div class="o_livechat_rating_feedback_text">
Did we correctly answer your question ?
</div>
<div class="oe_livechat_rating_choices">
<div class="o_livechat_rating_choices">
<img t-att-src="widget.server_origin + '/rating/static/src/img/rating_10.png'" alt="Good" data-value="10"/>
<img t-att-src="widget.server_origin + '/rating/static/src/img/rating_5.png'" alt="OK" data-value="5"/>
<img t-att-src="widget.server_origin + '/rating/static/src/img/rating_0.png'" alt="Bad" data-value="0" />
</div>
<div class="oe_livechat_rating_reason">
<div class="o_livechat_rating_reason">
<textarea id="reason" placeholder="Explain your note"></textarea>
<div class="oe_livechat_rating_reason_button">
<div class="o_livechat_rating_reason_button">
<input type="button" id="rating_submit" value="Send" />
</div>
</div>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<t t-extend="mail.chat.ChatMailThread.channels">
<t t-jquery="li > a" t-operation="append">
<t t-if="channel.channel_type === 'livechat'">
<span class="fa fa-times pull-right o_mail_chat_channel_unpin" t-att-title="_t('Untoggle')" t-att-data-channel-id="channel.id"/>
</t>
</t>
</t>
<t t-extend="mail.chat.ChatMailThread">
<t t-jquery=".o_mail_chat_sidebar" t-operation="append">
<!-- Livechat list -->
<h4 class="o_mail_chat_sidebar_title"><t t-esc="_t('Livechat')"/></h4>
<t t-call="mail.chat.ChatMailThread.channels">
<t t-set="channel_slot" t-value="'channel_livechat'"/>
</t>
</t>
</t>
</template>
@@ -2,6 +2,15 @@
<odoo>
<data>
<!--
Integrate Livechat Conversation in the Mail Chat Client Action
-->
<template id="assets_backend" name="im_livechat assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/im_livechat/static/src/js/im_livechat_backend.js"></script>
</xpath>
</template>
<!-- Template rendering the external HTML support page -->
<template id="support_page" name="Livechat Support Page">
&lt;!DOCTYPE html&gt;
@@ -76,9 +85,9 @@
<!-- css style -->
<link t-att-href="'%s/web/css/im_livechat.external_lib' % (url)" rel="stylesheet"/>
<!-- js of all the required lib (internal and external) -->
<script t-att-src="'%s/web/js/im_livechat.external_lib' % (url)" type="text/javascript" async="async" />
<script t-att-src="'%s/web/js/im_livechat.external_lib' % (url)" type="text/javascript" />
<!-- the loader -->
<script t-att-src="'%s/im_livechat/loader/%s/%i' % (url, dbname, channel)" type="text/javascript" async="async" />
<script t-att-src="'%s/im_livechat/loader/%s/%i' % (url, dbname, channel)" type="text/javascript" />
</template>
<!-- Template rendering all the scripts required to execute the Livechat from a page containing Odoo -->
@@ -128,17 +137,17 @@
<script type="text/javascript" src="/web/static/lib/moment/moment.js"></script>
<!-- IM module -->
<script type="text/javascript" src="/bus/static/src/js/bus.js"></script>
<script type="text/javascript" src="/im_chat/static/src/js/im_chat_common.js"></script>
<script type="text/javascript" src="/mail/static/src/js/mail_utils.js"></script>
<script type="text/javascript" src="/mail/static/src/js/mail_chat_common.js"></script>
<script type="text/javascript" src="/im_livechat/static/src/js/im_livechat.js"></script>
<!-- CSS -->
<link rel="stylesheet" href="/im_chat/static/src/css/im_chat_common.css"></link>
<link rel="stylesheet" href="/mail/static/src/css/mail_chat_common.css"></link>
<link rel="stylesheet" href="/im_livechat/static/src/css/im_livechat.css"/>
</template>
<!-- the js code to initialize the LiveSupport object -->
<template id="loader" name="Javascript initializing the LiveSupport">
<t t-translate="off">
$(function() {
odoo.define('im_livechat.livesupport', function (require) {
var im_livechat = require('im_livechat.im_livechat');
window.livesupport = new im_livechat.LiveSupport(
@@ -155,7 +164,6 @@
<t t-raw="rule"/>
);
});
});
</t>
</template>
@@ -29,7 +29,7 @@
<field name="web_page" widget="url"/>
<field name="are_you_inside"/>
<field name="user_ids"/>
<field name="nbr_session"/>
<field name="nbr_channel"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
@@ -44,7 +44,7 @@
<t t-esc="(record.user_ids.raw_value || []).length"/> Operators
<br/>
<i class="fa fa-comments"></i>
<t t-esc="record.nbr_session.raw_value"/> Sessions
<t t-esc="record.nbr_channel.raw_value"/> Sessions
</p>
</div>
<div class="o_kanban_button">
@@ -66,8 +66,8 @@
<form>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" type="action" attrs="{'invisible':[('nbr_session','=', 0)]}" name="%(im_chat_session_action_from_channel)d" icon="fa-comments">
<field string="Sessions" name="nbr_session" widget="statinfo"/>
<button class="oe_stat_button" type="action" attrs="{'invisible':[('nbr_channel','=', 0)]}" name="%(mail_channel_action_from_livechat_channel)d" icon="fa-comments">
<field string="Sessions" name="nbr_channel" widget="statinfo"/>
</button>
<button name="action_view_rating" attrs="{'invisible':[('rating_percentage_satisfaction','=', -1)]}" class="oe_stat_button" type="object" icon="fa-smile-o">
<field string="% Happy" name="rating_percentage_satisfaction" widget="statinfo"/>
@@ -192,7 +192,7 @@
id="session_history"
name="History"
parent="menu_livechat_root"
action="im_chat_session_action"
action="mail_channel_action"
groups="im_livechat_group_user"
sequence="10"/>
+23 -23
View File
@@ -2,9 +2,9 @@
<openerp>
<data>
<record id="im_chat_session_view_search" model="ir.ui.view">
<field name="name">im_chat.session.search</field>
<field name="model">im_chat.session</field>
<record id="mail_channel_view_search" model="ir.ui.view">
<field name="name">mail.channel.search</field>
<field name="model">mail.channel</field>
<field name="arch" type="xml">
<search string="Search history">
<filter name="id" string="My sessions" domain="[('user_ids','in', uid)]"/>
@@ -20,28 +20,28 @@
</field>
</record>
<record id="im_chat_session_view_tree" model="ir.ui.view">
<field name="name">im_chat.session.tree</field>
<field name="model">im_chat.session</field>
<record id="mail_channel_view_tree" model="ir.ui.view">
<field name="name">mail.channel.tree</field>
<field name="model">mail.channel</field>
<field name="arch" type="xml">
<tree string="History" create="false">
<field name="id"/>
<field name="fullname"/>
<field name="name"/>
<field name="create_date"/>
</tree>
</field>
</record>
<record id="im_chat_session_view_form" model="ir.ui.view">
<field name="name">im_chat.session.form</field>
<field name="model">im_chat.session</field>
<record id="mail_channel_view_form" model="ir.ui.view">
<field name="name">mail.channel.form</field>
<field name="model">mail.channel</field>
<field name="arch" type="xml">
<form string="Session Form" create="false" edit="false">
<sheet>
<group>
<group string="General">
<field name="id"/>
<field name="fullname" />
<field name="name" />
<field name="create_date" readonly="1"/>
</group>
<group string="Feedback">
@@ -68,8 +68,8 @@
<field name="message_ids" type="tree">
<tree string="History">
<field name="create_date" />
<field name="from_id" />
<field name="message" />
<field name="author_id" />
<field name="body" />
</tree>
</field>
</sheet>
@@ -78,25 +78,25 @@
</record>
<record id="im_chat_session_action" model="ir.actions.act_window">
<record id="mail_channel_action" model="ir.actions.act_window">
<field name="name">History</field>
<field name="res_model">im_chat.session</field>
<field name="res_model">mail.channel</field>
<field name="view_type">form</field>
<field name="view_mode">list,form</field>
<field name="domain">[('channel_id', '!=', None)]</field>
<field name="context">{'group_by': ['channel_id', 'create_date:month']}</field>
<field name="domain">[('livechat_channel_id', '!=', None)]</field>
<field name="context">{'group_by': ['livechat_channel_id', 'create_date:month']}</field>
</record>
<record id="im_chat_session_action_from_channel" model="ir.actions.act_window">
<record id="mail_channel_action_from_livechat_channel" model="ir.actions.act_window">
<field name="name">Sessions</field>
<field name="res_model">im_chat.session</field>
<field name="res_model">mail.channel</field>
<field name="view_mode">list,form</field>
<field name="domain">[('channel_id', 'in', [active_id])]</field>
<field name="domain">[('livechat_channel_id', 'in', [active_id])]</field>
<field name="context">{
'search_default_channel_id': [active_id],
'default_channel_id': active_id,
'search_default_livechat_channel_id': [active_id],
'default_livechat_channel_id': active_id,
}</field>
<field name="search_view_id" ref="im_chat_session_view_search"/>
<field name="search_view_id" ref="mail_channel_view_search"/>
</record>
</data>
+21 -5
View File
@@ -174,7 +174,7 @@ class Channel(models.Model):
self.filtered(lambda channel: channel.channel_type == 'direct_message').mapped('channel_last_seen_partner_ids').write({'is_pinned': True})
# apply shortcode (text only) subsitution
body = self.env['mail.shortcode'].apply_shortcode(body, shortcode_type='text')
message = super(Channel, self).message_post(body=body, message_type=message_type, subtype=subtype, content_subtype=content_subtype, **kwargs)
message = super(Channel, self.with_context(mail_create_nosubscribe=True)).message_post(body=body, subject=subject, message_type=message_type, subtype=subtype, parent_id=parent_id, attachments=attachments, content_subtype=content_subtype, **kwargs)
return message
#------------------------------------------------------
@@ -192,13 +192,22 @@ class Channel(models.Model):
""" Broadcast the current channel header to the given partner ids
:param partner_ids : the partner to notify
"""
notifications = self._channel_channel_notifications(partner_ids)
self.env['bus.bus'].sendmany(notifications)
@api.multi
def _channel_channel_notifications(self, partner_ids):
""" Generate the bus notifications of current channel for the given partner ids
:param partner_ids : the partner to send the current channel header
:returns list of bus notifications (tuple (bus_channe, message_content))
"""
notifications = []
for partner in self.env['res.partner'].browse(partner_ids):
user_id = partner.user_ids and partner.user_ids[0] or False
if user_id:
for channel_info in self.sudo(user_id).channel_info():
notifications.append([(self._cr.dbname, 'res.partner', partner.id), channel_info])
self.env['bus.bus'].sendmany(notifications)
return notifications
@api.multi
def _notify(self, message):
@@ -209,8 +218,16 @@ class Channel(models.Model):
:param : mail.message to broadcast
"""
message.ensure_one()
message_values = message.message_format()[0]
notifications = self._channel_message_notifications(message)
self.env['bus.bus'].sendmany(notifications)
@api.multi
def _channel_message_notifications(self, message):
""" Generate the bus notifications for the given message
:param message : the mail.message to sent
:returns list of bus notifications (tuple (bus_channe, message_content))
"""
message_values = message.message_format()[0]
notifications = []
for channel in self:
message_values['channel_ids'] = [channel.id]
@@ -218,7 +235,7 @@ class Channel(models.Model):
# add uuid to allow anonymous to listen
if channel.public == 'public':
notifications.append([channel.uuid, dict(message_values)])
self.env['bus.bus'].sendmany(notifications)
return notifications
@api.multi
def channel_info(self):
@@ -335,7 +352,6 @@ class Channel(models.Model):
session_state.write({
'fold_state': state,
'is_minimized': bool(state != 'closed'),
'seen_datetime': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), # TODO JEM (to check) set seen_datetime to read needaction
})
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), session_state.channel_id.channel_info()[0])
@@ -411,6 +411,7 @@ var ChatMailThread = Widget.extend(mail_thread.MailThreadMixin, ControlPanelMixi
'display_document_link': true,
'display_needaction_button': true,
'emoji_list': this.conv_manager.emoji_list,
'default_username': _t('Anonymous'),
});
this.emoji_set_substitution(this.conv_manager.emoji_list);
// channel business
@@ -27,7 +27,7 @@ var ConversationManager = Widget.extend({
this.options = _.clone(options) || {};
_.defaults(this.options, {
placeholder: _t("Say something..."),
default_username: _t("Visitor"),
default_username: _t("Anonymous"),
load_history: true,
focus: false,
});
@@ -171,6 +171,7 @@ var Conversation = Widget.extend({
this._super(parent);
this.options = _.defaults(options || {}, {
'emoji': {},
'default_username': _t('Anonymous'),
'placeholder': _t('Write something ...'),
'loading_history': true,
'focus': true,
+1
View File
@@ -552,6 +552,7 @@ var MailThreadMixin = {
'display_needaction_button': false,
'internal_subtypes': [],
'emoji_list': [],
'default_username': _t('Anonymous'),
});
},
start: function(){
@@ -41,9 +41,12 @@
<div class="o_mail_chat_im_message_core">
<!-- author -->
<span class="text-muted">
<t t-if="message.author_id">
<t t-if="message.author_id &amp;&amp; message.author_id[0]">
<strong><t t-esc="message.author_id[1]"/></strong> -
</t>
<t t-if="!message.author_id || !message.author_id[0]">
<strong><t t-esc="default_username || widget.options.default_username"/></strong> -
</t>
<t t-if="!message.author_id">
<strong><t t-esc="message.email_from"/></strong> -
</t>
+9 -5
View File
@@ -153,14 +153,18 @@
Note by
</t>
<!-- author : partner or email_from -->
<t t-if="message.author_id">
<strong class="o_mail_redirect" data-oe-model="res.partner" t-att-data-oe-id="message.author_id[0]"><t t-esc="message.author_id[1]"/></strong> -
<t t-if="message.author_id &amp;&amp; message.author_id[1]">
<strong class="o_mail_redirect" data-oe-model="res.partner" t-att-data-oe-id="message.author_id[0]"><t t-esc="message.author_id[1]"/></strong>
</t>
<t t-if="!message.author_id">
<strong><t t-esc="message.email_from"/></strong> -
<t t-if="message.author_id &amp;&amp; !message.author_id[1] &amp;&amp; message.email_from">
<strong><t t-esc="message.email_from"/></strong>
</t>
<t t-if="message.author_id &amp;&amp; !message.author_id[1] &amp;&amp; !message.email_from">
<strong><t t-esc="widget.options.default_username"/></strong>
</t>
<small t-att-title="moment(message.date, 'YYYY-MM-DD hh:mm:ss').format('LLL')">
<t t-esc="moment(message.date, 'YYYY-MM-DD hh:mm:ss').format('hh:mm')"/>
- <t t-esc="moment(message.date, 'YYYY-MM-DD hh:mm:ss').format('hh:mm')"/>
</small>
<t t-if="message.model &amp;&amp; (message.model != 'mail.channel') &amp;&amp; widget.options.display_document_link">
on <a class="o_mail_redirect" t-att-data-oe-model="message.model" t-att-data-oe-id="message.res_id" href="#"><t t-esc="message.record_name"/></a>
-1
View File
@@ -45,7 +45,6 @@ class Invite(models.TransientModel):
model_ids = self.env['ir.model'].search([('model', '=', wizard.res_model)])
model_name = model_ids.name_get()[0][1]
print "@@@@@", wizard.message
# send an email if option checked and if a message exists (do not send void emails)
if wizard.send_mail and wizard.message and not wizard.message == '<br>': # when deleting the message, cleditor keeps a <br>
# TDE FIXME: use a template + _notification methods
+1 -1
View File
@@ -19,7 +19,7 @@ class WebsiteLivechat(http.Controller):
@http.route('/livechat/channel/<model("im_livechat.channel"):channel>', type='http', auth='public', website=True)
def channel_rating(self, channel, **kw):
# get the last 100 ratings and the repartition per grade
ratings = request.env['rating.rating'].search([('res_model', '=', 'im_chat.session'), ('res_id', 'in', channel.sudo().session_ids.ids)], order='create_date desc', limit=100)
ratings = request.env['rating.rating'].search([('res_model', '=', 'mail.channel'), ('res_id', 'in', channel.sudo().session_ids.ids)], order='create_date desc', limit=100)
repartition = channel.sudo().session_ids.rating_get_grades()
# compute percentage