diff options
| author | Jérome Maes <jem@openerp.com> | 2015-08-10 16:23:44 +0200 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2015-09-01 20:16:09 +0200 |
| commit | 95b97d1010e2596c8dbe719382c77c6009819afa (patch) | |
| tree | 2a5d3b0400785eab023ec1c7768829482682cf24 /addons | |
| parent | 1d54858cbdd10dd6fbfbb3edc1e032317e2ba3f6 (diff) | |
[REF] im_livechat : refactoring, make it compatbile with the nw mail module, change reporting sql view (split them in 2), adapt view, ...
Diffstat (limited to 'addons')
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 |
