diff options
30 files changed, 531 insertions, 299 deletions
diff --git a/addons/bus/models/bus.py b/addons/bus/models/bus.py index 36882c5a5d8..b7d39b05ea3 100644 --- a/addons/bus/models/bus.py +++ b/addons/bus/models/bus.py @@ -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: diff --git a/addons/im_livechat/__openerp__.py b/addons/im_livechat/__openerp__.py index 68852c27db4..da672662eca 100644 --- a/addons/im_livechat/__openerp__.py +++ b/addons/im_livechat/__openerp__.py @@ -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, diff --git a/addons/im_livechat/controllers/main.py b/addons/im_livechat/controllers/main.py index 68d5fbb7f29..7a4944f54ef 100644 --- a/addons/im_livechat/controllers/main.py +++ b/addons/im_livechat/controllers/main.py @@ -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 diff --git a/addons/im_livechat/data/im_livechat_channel_data.xml b/addons/im_livechat/data/im_livechat_channel_data.xml index be5c2fedf6e..2c9aa88ad2d 100644 --- a/addons/im_livechat/data/im_livechat_channel_data.xml +++ b/addons/im_livechat/data/im_livechat_channel_data.xml @@ -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','>', 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','>', (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','>', 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"><img src='/rating/static/src/img/rating_0.png'/></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"><img src='/rating/static/src/img/rating_5.png'/></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"><img src='/rating/static/src/img/rating_10.png'/></field> <field name="description">Rating Smiley Good</field> diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py index 969d67f0909..59702b9d62d 100644 --- a/addons/im_livechat/models/im_livechat_channel.py +++ b/addons/im_livechat/models/im_livechat_channel.py @@ -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 diff --git a/addons/im_livechat/models/mail_channel.py b/addons/im_livechat/models/mail_channel.py index a5af87d5580..a55e33db3d3 100644 --- a/addons/im_livechat/models/mail_channel.py +++ b/addons/im_livechat/models/mail_channel.py @@ -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') - - @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) + channel_type = fields.Selection(selection_add=[('livechat', 'Livechat Conversation')]) + livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel') @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() diff --git a/addons/im_livechat/models/rating.py b/addons/im_livechat/models/rating.py index a7d1cf0531c..c0271a0bb34 100644 --- a/addons/im_livechat/models/rating.py +++ b/addons/im_livechat/models/rating.py @@ -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() - diff --git a/addons/im_livechat/report/__init__.py b/addons/im_livechat/report/__init__.py index edfc5d9deeb..02fe2345c7c 100644 --- a/addons/im_livechat/report/__init__.py +++ b/addons/im_livechat/report/__init__.py @@ -1,2 +1,3 @@ # -*- coding: utf-8 -*- -import im_livechat_report +import im_livechat_report_channel +import im_livechat_report_operator diff --git a/addons/im_livechat/report/im_livechat_report_channel.py b/addons/im_livechat/report/im_livechat_report_channel.py index 4fa0436b001..5e5c012753e 100644 --- a/addons/im_livechat/report/im_livechat_report_channel.py +++ b/addons/im_livechat/report/im_livechat_report_channel.py @@ -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 ) """) diff --git a/addons/im_livechat/report/im_livechat_report_channel_views.xml b/addons/im_livechat/report/im_livechat_report_channel_views.xml index 65ad87120dd..d8d9ed7dcfa 100644 --- a/addons/im_livechat/report/im_livechat_report_channel_views.xml +++ b/addons/im_livechat/report/im_livechat_report_channel_views.xml @@ -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','<=', 1)]"/> - <filter name="treated_session" string="Treated sessions" domain="[('nbr_speakers','>', 1)]"/> + <filter name="missed_session" string="Missed sessions" domain="[('nbr_speaker','<=', 1)]"/> + <filter name="treated_session" string="Treated sessions" domain="[('nbr_speaker','>', 1)]"/> <filter name="last_24h" string="Last 24h" domain="[('start_date','>', (context_today() - datetime.timedelta(days=1)).strftime('%%Y-%%m-%%d') )]"/> + <filter name="last_week" string="Last Week" domain="[('start_date','>', (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> diff --git a/addons/im_livechat/report/im_livechat_report_operator.py b/addons/im_livechat/report/im_livechat_report_operator.py new file mode 100644 index 00000000000..7a15c90ef11 --- /dev/null +++ b/addons/im_livechat/report/im_livechat_report_operator.py @@ -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 + ) + """) diff --git a/addons/im_livechat/report/im_livechat_report_operator_views.xml b/addons/im_livechat/report/im_livechat_report_operator_views.xml new file mode 100644 index 00000000000..902a16ab1ac --- /dev/null +++ b/addons/im_livechat/report/im_livechat_report_operator_views.xml @@ -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','>', (context_today() - datetime.timedelta(days=1)).strftime('%%Y-%%m-%%d') )]"/> + <filter name="last_week" string="Last Week" domain="[('start_date','>', (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> diff --git a/addons/im_livechat/security/im_livechat_channel_security.xml b/addons/im_livechat/security/im_livechat_channel_security.xml index 71c8b08739a..5fa0888b950 100644 --- a/addons/im_livechat/security/im_livechat_channel_security.xml +++ b/addons/im_livechat/security/im_livechat_channel_security.xml @@ -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"/> diff --git a/addons/im_livechat/security/ir.model.access.csv b/addons/im_livechat/security/ir.model.access.csv index f6cc4ed628d..7f0681c1673 100644 --- a/addons/im_livechat/security/ir.model.access.csv +++ b/addons/im_livechat/security/ir.model.access.csv @@ -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 diff --git a/addons/im_livechat/static/src/css/im_livechat.css b/addons/im_livechat/static/src/css/im_livechat.css index becceb34f7f..07040610e99 100644 --- a/addons/im_livechat/static/src/css/im_livechat.css +++ b/addons/im_livechat/static/src/css/im_livechat.css @@ -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; } diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js index d59f7b4a5cf..faabae44909 100644 --- a/addons/im_livechat/static/src/js/im_livechat.js +++ b/addons/im_livechat/static/src/js/im_livechat.js @@ -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"); diff --git a/addons/im_livechat/static/src/js/im_livechat_backend.js b/addons/im_livechat/static/src/js/im_livechat_backend.js new file mode 100644 index 00000000000..e3a03db1248 --- /dev/null +++ b/addons/im_livechat/static/src/js/im_livechat_backend.js @@ -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); + }, +}); + +}); diff --git a/addons/im_livechat/static/src/xml/im_livechat.xml b/addons/im_livechat/static/src/xml/im_livechat.xml index fb0b9ca3072..fc004cb4dcf 100644 --- a/addons/im_livechat/static/src/xml/im_livechat.xml +++ b/addons/im_livechat/static/src/xml/im_livechat.xml @@ -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> diff --git a/addons/im_livechat/static/src/xml/im_livechat_backend.xml b/addons/im_livechat/static/src/xml/im_livechat_backend.xml new file mode 100644 index 00000000000..a48027b6047 --- /dev/null +++ b/addons/im_livechat/static/src/xml/im_livechat_backend.xml @@ -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> diff --git a/addons/im_livechat/views/im_livechat_channel_templates.xml b/addons/im_livechat/views/im_livechat_channel_templates.xml index 767d3986e84..c4a3aa3351a 100644 --- a/addons/im_livechat/views/im_livechat_channel_templates.xml +++ b/addons/im_livechat/views/im_livechat_channel_templates.xml @@ -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"> <!DOCTYPE html> @@ -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> diff --git a/addons/im_livechat/views/im_livechat_channel_views.xml b/addons/im_livechat/views/im_livechat_channel_views.xml index 06f8e3ec6de..9a49b91580a 100644 --- a/addons/im_livechat/views/im_livechat_channel_views.xml +++ b/addons/im_livechat/views/im_livechat_channel_views.xml @@ -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"/> diff --git a/addons/im_livechat/views/mail_channel_views.xml b/addons/im_livechat/views/mail_channel_views.xml index fc864ddf0e6..159e6d7695d 100644 --- a/addons/im_livechat/views/mail_channel_views.xml +++ b/addons/im_livechat/views/mail_channel_views.xml @@ -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> diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 6eeebcef21d..0ef1c567110 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -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]) diff --git a/addons/mail/static/src/js/mail_chat_backend.js b/addons/mail/static/src/js/mail_chat_backend.js index 9353afb68ab..60c18b0282a 100644 --- a/addons/mail/static/src/js/mail_chat_backend.js +++ b/addons/mail/static/src/js/mail_chat_backend.js @@ -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 diff --git a/addons/mail/static/src/js/mail_chat_common.js b/addons/mail/static/src/js/mail_chat_common.js index a5f52ddf4b1..119c84dd0d7 100644 --- a/addons/mail/static/src/js/mail_chat_common.js +++ b/addons/mail/static/src/js/mail_chat_common.js @@ -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, diff --git a/addons/mail/static/src/js/mail_thread.js b/addons/mail/static/src/js/mail_thread.js index 58d63fceda8..99ce20ebf5a 100644 --- a/addons/mail/static/src/js/mail_thread.js +++ b/addons/mail/static/src/js/mail_thread.js @@ -552,6 +552,7 @@ var MailThreadMixin = { 'display_needaction_button': false, 'internal_subtypes': [], 'emoji_list': [], + 'default_username': _t('Anonymous'), }); }, start: function(){ diff --git a/addons/mail/static/src/xml/mail_chat_common.xml b/addons/mail/static/src/xml/mail_chat_common.xml index 745893c11e1..461a6f94d06 100644 --- a/addons/mail/static/src/xml/mail_chat_common.xml +++ b/addons/mail/static/src/xml/mail_chat_common.xml @@ -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 && 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> diff --git a/addons/mail/static/src/xml/mail_thread.xml b/addons/mail/static/src/xml/mail_thread.xml index 7dee5098b3c..88539e87576 100644 --- a/addons/mail/static/src/xml/mail_thread.xml +++ b/addons/mail/static/src/xml/mail_thread.xml @@ -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 && 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 && !message.author_id[1] && message.email_from"> + <strong><t t-esc="message.email_from"/></strong> </t> + <t t-if="message.author_id && !message.author_id[1] && !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 && (message.model != 'mail.channel') && 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> diff --git a/addons/mail/wizard/invite.py b/addons/mail/wizard/invite.py index ce29f1d600b..8ad772a04cc 100644 --- a/addons/mail/wizard/invite.py +++ b/addons/mail/wizard/invite.py @@ -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 diff --git a/addons/website_livechat/controllers/main.py b/addons/website_livechat/controllers/main.py index 2b3fdbed712..64f7a7f3d5a 100644 --- a/addons/website_livechat/controllers/main.py +++ b/addons/website_livechat/controllers/main.py @@ -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 |
