diff --git a/addons/bus/controllers/main.py b/addons/bus/controllers/main.py index ee9902ec983..3da386e80b2 100644 --- a/addons/bus/controllers/main.py +++ b/addons/bus/controllers/main.py @@ -20,6 +20,9 @@ class BusController(openerp.http.Controller): # override to add channels def _poll(self, dbname, channels, last, options): + # update the user presence + if request.session.uid and 'im_presence' in options: + request.env['bus.presence'].update(options.get('im_presence')) request.cr.close() request._cr = None return dispatch.poll(dbname, channels, last) diff --git a/addons/bus/models/__init__.py b/addons/bus/models/__init__.py index c1c365d98c0..3e2808e6e2e 100644 --- a/addons/bus/models/__init__.py +++ b/addons/bus/models/__init__.py @@ -1,2 +1,5 @@ # -*- coding: utf-8 -*- import bus +import bus_presence +import res_users +import res_partner 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/bus/models/bus_presence.py b/addons/bus/models/bus_presence.py new file mode 100644 index 00000000000..445e9491a75 --- /dev/null +++ b/addons/bus/models/bus_presence.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +import datetime +import random +import time + +from openerp import api, fields, models +from openerp import tools +from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT + +from openerp.addons.bus.models.bus import TIMEOUT + +DISCONNECTION_TIMER = TIMEOUT + 5 +AWAY_TIMER = 600 # 10 minutes + + +class BusPresence(models.Model): + """ User Presence + Its status is 'online', 'away' or 'offline'. This model should be a one2one, but is not + attached to res_users to avoid database concurrence errors. Since the 'update' method is executed + at each poll, if the user have multiple opened tabs, concurrence errors can happend, but are 'muted-logged'. + """ + + _name = 'bus.presence' + _description = 'User Presence' + + _sql_constraints = [('bus_user_presence_unique', 'unique(user_id)', 'A user can only have one IM status.')] + + user_id = fields.Many2one('res.users', 'Users', required=True, index=True, ondelete='cascade') + last_poll = fields.Datetime('Last Poll', default=lambda self: fields.Datetime.now()) + last_presence = fields.Datetime('Last Presence', default=lambda self: fields.Datetime.now()) + status = fields.Selection([('online', 'Online'), ('away', 'Away'), ('offline', 'Offline')], 'IM Status', default='offline') + + + @api.model + def update(self, user_presence=True): + """ Register the given presence of the current user, and trigger a im_status change if necessary. + The status will not be written or sent if not necessary. + :param user_presence : True, if the user (self._uid) is still detected using its browser. + :type user_presence : boolean + """ + presence = self.search([('user_id', '=', self._uid)], limit=1) + # set the default values + send_notification = True + values = { + 'last_poll': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'status': presence and presence.status or 'offline' + } + # update the user or a create a new one + if not presence: # create a new presence for the user + values['status'] = 'online' + values['user_id'] = self._uid + self.create(values) + else: # write the user presence if necessary + if user_presence: + values['last_presence'] = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + values['status'] = 'online' + else: + threshold = datetime.datetime.now() - datetime.timedelta(seconds=AWAY_TIMER) + if datetime.datetime.strptime(presence.last_presence, DEFAULT_SERVER_DATETIME_FORMAT) < threshold: + values['status'] = 'away' + send_notification = presence.status != values['status'] + # write only if the last_poll is passed TIMEOUT, or if the status has changed + delta = datetime.datetime.utcnow() - datetime.datetime.strptime(presence.last_poll, DEFAULT_SERVER_DATETIME_FORMAT) + if delta > datetime.timedelta(seconds=TIMEOUT) or send_notification: + # Hide transaction serialization errors, which can be ignored, the presence update is not essential + with tools.mute_logger('openerp.sql_db'): + presence.write(values) + # avoid TransactionRollbackError + self.env.cr.commit() # TODO : check if still necessary + # notify if the status has changed + if send_notification: # TODO : add user_id to the channel tuple to allow using user_watch in controller presence + self.env['bus.bus'].sendone((self._cr.dbname, 'bus.presence'), {'id': self._uid, 'im_status': values['status']}) + # gc : disconnect the users having a too old last_poll. 1 on 100 chance to do it. + if random.random() < 0.01: + self.check_users_disconnection() + return True + + @api.model + def check_users_disconnection(self): + """ Disconnect the users having a too old last_poll """ + limit_date = (datetime.datetime.utcnow() - datetime.timedelta(0, DISCONNECTION_TIMER)).strftime(DEFAULT_SERVER_DATETIME_FORMAT) + presences = self.search([('last_poll', '<', limit_date), ('status', '!=', 'offline')]) + presences.write({'status': 'offline'}) + notifications = [] + for presence in presences: + notifications.append([(self._cr.dbname, 'bus.presence'), {'id': presence.user_id.id, 'im_status': presence.status}]) + self.env['bus.bus'].sendmany(notifications) diff --git a/addons/bus/models/res_partner.py b/addons/bus/models/res_partner.py new file mode 100644 index 00000000000..358f2a41298 --- /dev/null +++ b/addons/bus/models/res_partner.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +from openerp import api, fields, models + +class ResPartner(models.Model): + _inherit = 'res.partner' + + im_status = fields.Char('IM Status', compute='_compute_im_status') + + @api.multi + def _compute_im_status(self): + self.env.cr.execute(""" + SELECT P.id as id, B.status as im_status + FROM bus_presence B + JOIN res_users U ON B.user_id = U.id + JOIN res_partner P ON P.id = U.partner_id + WHERE P.id IN %s AND U.active = 't' + """, (tuple(self.ids),)) + fetch_result = self.env.cr.dictfetchall() + result = dict(((user_presence['id'], user_presence['im_status']) for user_presence in fetch_result)) + for partner in self: + partner.im_status = result.get(partner.id, 'offline') + + @api.model + def im_search(self, name, limit=20): + """ Search partner with a name and return its id, name and im_status. + Note : the user must be logged + :param name : the partner name to search + :param limit : the limit of result to return + """ + name = '%' + name + '%' + excluded_partner_ids = [self.env.user.partner_id.id] + self.env.cr.execute(""" + SELECT U.id as user_id, P.id as id, P.name as name, COALESCE(B.status, 'offline') as im_status + FROM res_users U + JOIN res_partner P ON P.id = U.partner_id + LEFT JOIN bus_presence B ON B.user_id = U.id + WHERE P.name ILIKE %s + AND P.id NOT IN %s + AND U.active = 't' + LIMIT %s + """, (name, tuple(excluded_partner_ids), limit)) + result = self.env.cr.dictfetchall() + return result diff --git a/addons/im_chat/models/res_users.py b/addons/bus/models/res_users.py similarity index 93% rename from addons/im_chat/models/res_users.py rename to addons/bus/models/res_users.py index 5e9d664b6a8..2b7d7ccfe49 100644 --- a/addons/im_chat/models/res_users.py +++ b/addons/bus/models/res_users.py @@ -12,7 +12,7 @@ class ResUsers(models.Model): def _compute_im_status(self): """ Compute the im_status of the users """ res = dict((uid, 'offline') for uid in self.ids) - presences = self.env['im_chat.presence'].search([('user_id', 'in', self.ids)]) + presences = self.env['bus.presence'].search([('user_id', 'in', self.ids)]) for presence in presences: res[presence.user_id.id] = presence.status for user in self: @@ -37,7 +37,7 @@ class ResUsers(models.Model): # first query to find online employee self._cr.execute('''SELECT U.id as id, P.name as name, COALESCE(S.status, 'offline') as im_status - FROM im_chat_presence S + FROM bus_presence S JOIN res_users U ON S.user_id = U.id JOIN res_partner P ON P.id = U.partner_id WHERE '''+where_clause_base+''' @@ -52,7 +52,7 @@ class ResUsers(models.Model): # second query to find other online people if len(result) < limit: self._cr.execute('''SELECT U.id as id, P.name as name, COALESCE(S.status, 'offline') as im_status - FROM im_chat_presence S + FROM bus_presence S JOIN res_users U ON S.user_id = U.id JOIN res_partner P ON P.id = U.partner_id WHERE '''+where_clause_base+''' @@ -67,7 +67,7 @@ class ResUsers(models.Model): if len(result) < limit: self._cr.execute('''SELECT U.id as id, P.name as name, COALESCE(S.status, 'offline') as im_status FROM res_users U - LEFT JOIN im_chat_presence S ON S.user_id = U.id + LEFT JOIN bus_presence S ON S.user_id = U.id LEFT JOIN res_partner P ON P.id = U.partner_id WHERE '''+where_clause_base+''' AND U.id NOT IN %s diff --git a/addons/bus/security/ir.model.access.csv b/addons/bus/security/ir.model.access.csv index 8a679b74f81..4a626a0eb5a 100644 --- a/addons/bus/security/ir.model.access.csv +++ b/addons/bus/security/ir.model.access.csv @@ -1,2 +1,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_bus_bus,bus.bus public,model_bus_bus,,0,0,0,0 \ No newline at end of file +access_bus_bus,bus.bus public,model_bus_bus,,0,0,0,0 +access_bus_presence,bus.presence,model_bus_presence,base.group_user,1,1,1,1 +access_bus_presence_portal,bus.presence,model_bus_presence,base.group_portal,1,1,1,1 diff --git a/addons/bus/static/src/js/bus.js b/addons/bus/static/src/js/bus.js index b0756132012..3c828668308 100644 --- a/addons/bus/static/src/js/bus.js +++ b/addons/bus/static/src/js/bus.js @@ -9,12 +9,21 @@ bus.ERROR_DELAY = 10000; bus.Bus = Widget.extend({ init: function(){ + var self = this; this._super(); this.options = {}; this.activated = false; this.channels = []; this.last = 0; this.stop = false; + + // bus presence + this.set("window_focus", true); + this.on("change:window_focus", self, function(e) { + self.options.im_presence = self.get("window_focus"); + }); + $(window).on("focus", _.bind(this.window_focus, this)); + $(window).on("blur", _.bind(this.window_blur, this)); }, start_polling: function(){ if(!this.activated){ @@ -57,6 +66,13 @@ bus.Bus = Widget.extend({ delete_channel: function(channel){ this.channels = _.without(this.channels, channel); }, + // bus presence : window focus/unfocus + window_focus: function() { + this.set("window_focus", true); + }, + window_blur: function() { + this.set("window_focus", false); + }, }); // singleton diff --git a/addons/gamification/__openerp__.py b/addons/gamification/__openerp__.py index c73a73c02d5..cc1a06d8feb 100644 --- a/addons/gamification/__openerp__.py +++ b/addons/gamification/__openerp__.py @@ -35,5 +35,4 @@ Both goals and badges are flexibles and can be adapted to a large range of modul ], 'application': False, 'auto_install': False, - 'qweb': ['static/src/xml/gamification.xml'], } diff --git a/addons/gamification/static/src/css/gamification.css b/addons/gamification/static/src/css/gamification.css index a2c416ffc2d..68479e58760 100644 --- a/addons/gamification/static/src/css/gamification.css +++ b/addons/gamification/static/src/css/gamification.css @@ -1,146 +1,65 @@ -@charset "UTF-8"; .openerp .oe_kanban_view .oe_kanban_card.oe_kanban_goal { width: 230px; - min-height: 200px; } + min-height: 200px; +} .openerp .oe_kanban_view .oe_kanban_card.oe_kanban_badge { width: 250px; - min-height: 150px; } + min-height: 150px; +} .openerp .oe_kanban_badge_avatars { - margin-top: 8px; } - .openerp .oe_kanban_badge_avatars img { - width: 30px; - height: 30px; - padding-left: 0; - margin-top: 3px; - border-radius: 2px; - -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); } + margin-top: 8px; +} +.openerp .oe_kanban_badge_avatars img { + width: 30px; + height: 30px; + padding-left: 0; + margin-top: 3px; + border-radius: 2px; + -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} .openerp .oe_margin_top_8 { - margin-top: 8px; } + margin-top: 8px; +} .openerp .oe_margin_top_16 { - margin-top: 16px; } + margin-top: 16px; +} .openerp .oe_kanban_goal .oe_goal_state_block { width: 200px; height: 130px; margin: auto; - margin-bottom: -20px; } - .openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state { - font-size: 2.5em; - font-weight: bold; - padding-top: 30px; } - .openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state .fa { - font-size: 3em; - margin-top: -17px; - margin-left: 17px; } -.openerp .oe_kanban_goal .oe_goal_state_block, .openerp .oe_kanban_goal p { - text-align: center; } -.openerp .oe_kanban_content .oe_goal_gauge:first-child { - margin: auto; } -.openerp .oe_kanban_content .oe_goal_gauge svg { - margin-top: -20px; } -.openerp .oe_no_overflow { - overflow: hidden; } -.openerp .oe_red { - color: red; } -.openerp .oe_green { - color: green; } -.openerp .oe_orange { - color: orange; } -.openerp .oe_form td .oe_no_padding { - margin-left: -6px; } -.openerp .oe_timeline .oe_tl_inbox_aside .oe_gamification_suggestion { - background-color: #d3def1; } - .openerp .oe_timeline .oe_tl_inbox_aside .oe_gamification_suggestion ul { - padding-left: 15px; } - .openerp .oe_timeline .oe_tl_inbox_aside .oe_gamification_suggestion span { - background-color: #f0eeee; - display: inline-block; - height: 25px; - vertical-align: middle; - font-size: 14px; - font-weight: bold; - margin-top: -20px; - padding-left: 0 5px 0px 5px; } -.openerp .oe_timeline .oe_goal { - background-color: #f0eeee; - border: solid 1px #cacaca; - margin-top: 20px; - padding-left: 10px; } - .openerp .oe_timeline .oe_goal .oe_update_challenge.fa, .openerp .oe_timeline .oe_goal .oe_goal_action.fa { - visibility: hidden; - font-size: 16px; - float: right; - position: relative; } - .openerp .oe_timeline .oe_goal div:hover > .oe_update_challenge, .openerp .oe_timeline .oe_goal div:hover > .oe_goal_action, .openerp .oe_timeline .oe_goal th:hover > .oe_goal_action { - visibility: visible; } - .openerp .oe_timeline .oe_goal .oe_goals_list { - padding-left: 0; - margin-top: 5px; - margin-bottom: 10px; - width: 100%; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_no_progress div { - display: inline-block; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_no_progress::before { - content: "•"; - padding-right: 4px; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_no_progress.oe_goal_reached::before { - content: "✓"; - padding-right: 0; - margin-left: -2px; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_no_progress.oe_goal_reached .oe_cell { - text-decoration: line-through; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_cell.oe_goal_current { - font-size: 150%; - font-weight: bold; - min-width: 50px; - padding: 0 5px; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_goal_outer_box { - display: inline-block; - position: relative; - z-index: 0; - vertical-align: middle; - width: 100%; - border: solid 1px rgba(0, 0, 0, 0.03); - border-radius: 2px; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_goal_outer_box.oe_no_progress { - border: none; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_goal_progress_background { - background-color: white; - position: absolute; - height: 100%; - width: 100%; - z-index: -2; - top: 0; - left: 0; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_goal_progress { - background-color: #d4e9de; - position: absolute; - height: 100%; - width: 0; - z-index: -1; - top: 0; - left: 0; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_thead { - font-weight: normal; - padding: 5px; - text-align: center; } - .openerp .oe_timeline .oe_goal .oe_goals_list .oe_cell { - padding: 3px 0; } -.openerp .oe_timeline .oe_table.oe_goals_list .col0 { - font-size: 200%; + margin-bottom: -20px; +} +.openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state { + font-size: 2.5em; font-weight: bold; - width: 25px; - text-align: center; } -.openerp .oe_timeline .oe_table.oe_goals_list .col1 { - padding: 0 5px; } -.openerp .oe_timeline .oe_table.oe_goals_list .col2 { - width: auto; } -.openerp .oe_timeline .oe_user_avatar { - width: 24px; - padding-right: 5px; } -.openerp .oe_timeline .oe_table { - display: table; } -.openerp .oe_timeline .oe_row { - display: table-row; } -.openerp .oe_timeline .oe_cell { - display: table-cell; - vertical-align: middle; } + padding-top: 30px; +} +.openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state .fa { + font-size: 3em; + margin-top: -17px; + margin-left: 17px; +} +.openerp .oe_kanban_goal .oe_goal_state_block, .openerp .oe_kanban_goal p { + text-align: center; +} +.openerp .oe_kanban_content .oe_goal_gauge:first-child { + margin: auto; +} +.openerp .oe_kanban_content .oe_goal_gauge svg { + margin-top: -20px; +} +.openerp .oe_no_overflow { + overflow: hidden; +} +.openerp .oe_red { + color: red; +} +.openerp .oe_green { + color: green; +} +.openerp .oe_orange { + color: orange; +} +.openerp .oe_form td .oe_no_padding { + margin-left: -6px; +} diff --git a/addons/gamification/static/src/css/gamification.sass b/addons/gamification/static/src/css/gamification.sass index b524c9486b8..cbba41e05fb 100644 --- a/addons/gamification/static/src/css/gamification.sass +++ b/addons/gamification/static/src/css/gamification.sass @@ -61,118 +61,3 @@ // compensate padding from .openerp .oe_form td.oe_form_group_cell + .oe_form_group_cell .oe_form td .oe_no_padding margin-left: -6px - - .oe_timeline - .oe_tl_inbox_aside - .oe_gamification_suggestion - background-color: #d3def1 - - ul - padding-left: 15px - - span - background-color: #f0eeee - display: inline-block - height: 25px - vertical-align: middle - font-size: 14px - font-weight: bold - margin-top: -20px - padding-left: 0 5px 0px 5px - - .oe_goal - background-color: #f0eeee - border: solid 1px #CACACA - margin-top: 20px - padding-left: 10px - .oe_update_challenge.fa, .oe_goal_action.fa - visibility: hidden - font-size: 16px - float: right - position: relative - div:hover > .oe_update_challenge, div:hover > .oe_goal_action, th:hover > .oe_goal_action - visibility: visible - .oe_goals_list - padding-left: 0 - margin-top: 5px - margin-bottom: 10px - width: 100% - - .oe_no_progress - div - display: inline-block - .oe_no_progress::before - content: "•" - padding-right: 4px - .oe_no_progress.oe_goal_reached::before - content: "✓" - padding-right: 0 - margin-left: -2px - .oe_no_progress.oe_goal_reached - .oe_cell - text-decoration: line-through - - .oe_cell.oe_goal_current - font-size: 150% - font-weight: bold - min-width: 50px - padding: 0 5px - - .oe_goal_outer_box - display: inline-block - position: relative - z-index: 0 - vertical-align: middle - width: 100% - border: solid 1px rgba(0,0,0,0.03) - border-radius: 2px - .oe_goal_outer_box.oe_no_progress - border: none - - .oe_goal_progress_background - background-color: white - position: absolute - height: 100% - width: 100% - z-index: -2 - top: 0 - left: 0 - - .oe_goal_progress - background-color: rgb(212, 233, 222) - position: absolute - height: 100% - width: 0 - z-index: -1 - top: 0 - left: 0 - - .oe_thead - font-weight: normal - padding: 5px - text-align: center - .oe_cell - padding: 3px 0 - - .oe_table.oe_goals_list - .col0 - font-size: 200% - font-weight: bold - width: 25px - text-align: center - .col1 - padding: 0 5px - .col2 - width: auto - - .oe_user_avatar - width: 24px - padding-right: 5px - - .oe_table - display: table - .oe_row - display: table-row - .oe_cell - display: table-cell - vertical-align: middle \ No newline at end of file diff --git a/addons/gamification/static/src/js/gamification.js b/addons/gamification/static/src/js/gamification.js deleted file mode 100644 index 1a096d2e681..00000000000 --- a/addons/gamification/static/src/js/gamification.js +++ /dev/null @@ -1,122 +0,0 @@ -odoo.define('gamification.gamification', function(require) { -"use strict"; - -var mail = require('mail.mail'); -var core = require('web.core'); -var form_common = require('web.form_common'); -var Model = require('web.DataModel'); -var session = require('web.session'); -var Widget = require('web.Widget'); - -var QWeb = core.qweb; - -var Sidebar = Widget.extend({ - template: 'gamification.UserWallSidebar', - init: function (parent, action) { - this._super(parent, action); - this.deferred = $.Deferred(); - this.goals_info = {}; - this.challenge_suggestions = {}; - $(document).off('keydown.klistener'); - }, - events: { - // update a challenge and related goals - 'click i.oe_update_challenge': function(event) { - var self = this; - var challenge_id = parseInt(event.currentTarget.id, 10); - var goals_updated = new Model('gamification.challenge').call('quick_update', [challenge_id]); - $.when(goals_updated).done(function() { - self.get_goal_todo_info(); - }); - }, - // action to modify a goal - 'click a.oe_goal_action': function(event) { - var self = this; - var goal_id = parseInt(event.currentTarget.id, 10); - var goal_action = new Model('gamification.goal').call('get_action', [goal_id]).then(function(res) { - goal_action.action = res; - }); - $.when(goal_action).done(function() { - var action = self.do_action(goal_action.action); - $.when(action).done(function () { - new Model('gamification.goal').call('update', [[goal_id]]).then(function(res) { - self.get_goal_todo_info(); - }); - }); - }); - }, - // get more info about a challenge request - 'click a.oe_challenge_reply': function(event) { - var self = this; - var challenge_id = parseInt(event.currentTarget.id, 10); - var challenge_action = new Model('gamification.challenge').call('reply_challenge_wizard', [challenge_id]).then(function(res) { - challenge_action.action = res; - }); - $.when(challenge_action).done(function() { - self.do_action(challenge_action.action).done(function () { - self.get_goal_todo_info(); - }); - }); - } - }, - start: function() { - var self = this; - this._super.apply(this, arguments); - self.get_goal_todo_info(); - self.get_challenge_suggestions(); - }, - get_goal_todo_info: function() { - var self = this; - var challenges = new Model('res.users').call('get_serialised_gamification_summary', []).then(function(result) { - if (result.length === 0) { - self.$el.find(".oe_gamification_challenge_list").hide(); - } else { - self.$el.find(".oe_gamification_challenge_list").empty(); - _.each(result, function(item){ - var $item = $(QWeb.render("gamification.ChallengeSummary", {challenge: item})); - self.render_money_fields($item); - self.render_user_avatars($item); - self.$el.find('.oe_gamification_challenge_list').append($item); - }); - } - }); - }, - get_challenge_suggestions: function() { - var self = this; - var challenge_suggestions = new Model('res.users').call('get_challenge_suggestions', []).then(function(result) { - if (result.length === 0) { - self.$el.find(".oe_gamification_suggestion").hide(); - } else { - var $item = $(QWeb.render("gamification.ChallengeSuggestion", {challenges: result})); - self.$el.find('.oe_gamification_suggestion').append($item); - } - }); - }, - render_money_fields: function(item) { - var self = this; - var FieldMonetary = core.form_widget_registry.get('monetary'); - self.dfm = new form_common.DefaultFieldManager(self); - // Generate a FieldMonetary for each .oe_goal_field_monetary - item.find(".oe_goal_field_monetary").each(function() { - var currency_id = parseInt( $(this).attr('data-id'), 10); - var money_field = new FieldMonetary(self.dfm, { - attrs: { - modifiers: '{"readonly": true}' - } - }); - money_field.set('currency', currency_id); - money_field.get_currency_info(); - money_field.set('value', parseInt($(this).text(), 10)); - money_field.replace($(this)); - }); - }, - render_user_avatars: function(item) { - item.find(".oe_user_avatar").each(function() { - var user_id = parseInt( $(this).attr('data-id'), 10); - var url = session.url('/web/image', {model: 'res.users', field: 'image_small', id: user_id}); - $(this).attr("src", url); - }); - } -}); - -}); diff --git a/addons/gamification/static/src/xml/gamification.xml b/addons/gamification/static/src/xml/gamification.xml deleted file mode 100644 index 570cfe936d1..00000000000 --- a/addons/gamification/static/src/xml/gamification.xml +++ /dev/null @@ -1,114 +0,0 @@ - - -
-
-
-
-
- -
-
- - -
- - -
-
- -
-
-
-
-
- - - - - - -
-
- - Target: - - - Target: <= - - - -
-
-
-
-
-
- - -
-
-
- -
-
- - Target: - - - Target: <= - - -
-
-
-
-
- -
-
-
- -
-
-
- - -
- - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
- - -
-

Invited Challenges

-
    -
  • - -
  • -
-
-
- -
diff --git a/addons/gamification/views/gamification.xml b/addons/gamification/views/gamification.xml index 1e2f0096ea5..e99848b9f9d 100644 --- a/addons/gamification/views/gamification.xml +++ b/addons/gamification/views/gamification.xml @@ -4,7 +4,6 @@ diff --git a/addons/hr/__openerp__.py b/addons/hr/__openerp__.py index d8a0d34fe8a..6fecf886d24 100644 --- a/addons/hr/__openerp__.py +++ b/addons/hr/__openerp__.py @@ -41,7 +41,6 @@ You can manage: 'hr_data.xml', 'hr_tip_data.xml', 'res_config_view.xml', - 'mail_hr_view.xml', 'views/hr.xml', 'hr_dashboard.xml', ], diff --git a/addons/hr/mail_hr_view.xml b/addons/hr/mail_hr_view.xml deleted file mode 100644 index 39c28cfa07f..00000000000 --- a/addons/hr/mail_hr_view.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - Inbox - mail.message - form - timeline - { - 'default_model': 'res.users', - 'default_res_id': uid, - 'thread_model': 'res.partner', - 'options' : {'view_mailbox': True, - 'view_inbox': True, - 'show_write_to_followers': True} - } - [ - ('needaction', '=', True) - ] - - - \ No newline at end of file diff --git a/addons/im_chat/__init__.py b/addons/im_chat/__init__.py deleted file mode 100644 index 14ce547a094..00000000000 --- a/addons/im_chat/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -import models -import controllers diff --git a/addons/im_chat/__openerp__.py b/addons/im_chat/__openerp__.py deleted file mode 100644 index c7d615e447e..00000000000 --- a/addons/im_chat/__openerp__.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -{ - 'name' : 'Instant Messaging', - 'version': '1.0', - 'summary': 'Chat between users', - 'sequence': 115, - 'category': 'Tools', - 'complexity': 'easy', - 'website': 'https://www.odoo.com/page/live-chat', - 'description': - """ -Instant Messaging -================= -Allows users to chat with each other in real time. Find other users easily and -chat in real time. It support several chats in parallel. - """, - 'data': [ - 'security/ir.model.access.csv', - 'security/im_chat_session_security.xml', - 'views/im_chat_session_templates.xml', - 'views/im_chat_session_views.xml', - 'data/im_chat_session_data.xml', - ], - 'depends': ['base', 'web', 'bus'], - 'qweb': ['static/src/xml/*.xml'], - 'application': True, - 'installable': True, -} diff --git a/addons/im_chat/controllers/__init__.py b/addons/im_chat/controllers/__init__.py deleted file mode 100644 index 12d2d759f34..00000000000 --- a/addons/im_chat/controllers/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -* -import main diff --git a/addons/im_chat/controllers/main.py b/addons/im_chat/controllers/main.py deleted file mode 100644 index 35ede172690..00000000000 --- a/addons/im_chat/controllers/main.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -* -import base64 -import openerp - -from openerp import SUPERUSER_ID -from openerp.http import request - - -class ChatController(openerp.addons.bus.controllers.main.BusController): - - def _default_request_uid(self): - """ For Anonymous people, they receive the access right of SUPERUSER_ID since they have NO access (auth=none) - !!! Each time a method from this controller is call, there is a check if the user (who can be anonymous and Sudo access) - can access to the ressource, thanks to 'is_in_session', (for instance). - """ - return request.session.uid and request.session.uid or SUPERUSER_ID - - # -------------------------- - # Extends BUS Controller Poll - # -------------------------- - def _poll(self, dbname, channels, last, options): - if request.session.uid: - request.env['im_chat.presence'].update(options.get('im_presence')) - # channel to receive message - channels.append((request.db, 'im_chat.session', request.session.uid)) - ## For performance issue, the real time status notification is disabled. This means a change of status are still braoadcasted - ## but not received by anyone. Otherwise, all listening user restart their longpolling at the same time and cause a 'ConnectionPool Full Error' - ## since there is not enought cursors for everyone. Now, when a user open his list of users, an RPC call is made to update his user status list. - ##channels.append((request.db,'im_chat.presence')) - return super(ChatController, self)._poll(dbname, channels, last, options) - - # -------------------------- - # Anonymous routes (Common Methods) - # -------------------------- - @openerp.http.route('/im_chat/post', type="json", auth="none") - def post(self, uuid, message_content, message_type): - request_uid = self._default_request_uid() - return request.env["im_chat.message"].sudo(request_uid).send_message(request.session.uid, uuid, message_content, message_type) - - @openerp.http.route(['/im_chat/image//', '/im_chat/image//'], type='http', auth="none") - def image(self, uuid, user_id): - request_uid = self._default_request_uid() - # anonymous will have a user_id = False, interpreted by string - if isinstance(user_id, (basestring, unicode)): - user_id = False - # get image for the people in the channel - image_b64 = request.env['im_chat.session'].sudo(request_uid).session_user_image(uuid, user_id) - image_data = base64.b64decode(image_b64) - headers = [('Content-Type', 'image/png')] - headers.append(('Content-Length', len(image_data))) - return request.make_response(image_data, headers) - - @openerp.http.route(['/im_chat/history'], type="json", auth="none") - def history(self, uuid, last_id=False, limit=20): - request_uid = self._default_request_uid() - return request.env["im_chat.message"].sudo(request_uid).fetch_message(uuid, last_id, limit) - - # -------------------------- - # User routes (Only Logged User Methods) - # -------------------------- - @openerp.http.route('/im_chat/init', type="json", auth="user") - def init(self): - return request.env['im_chat.message'].get_init_notifications() diff --git a/addons/im_chat/data/im_chat_session_data.xml b/addons/im_chat/data/im_chat_session_data.xml deleted file mode 100644 index bd77ecbf8da..00000000000 --- a/addons/im_chat/data/im_chat_session_data.xml +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - :) - <img src='/im_chat/static/src/img/smile.png'/> - Emoji Smile - - - - :( - <img src='/im_chat/static/src/img/sad.png'/> - Emoji Sad - - - - :D - <img src='/im_chat/static/src/img/laugh.png'/> - Emoji Laugh - - - - ;) - <img src='/im_chat/static/src/img/wink.png'/> - Emoji Wink - - - - O_0 - <img src='/im_chat/static/src/img/stuck_out_tongue.png'/> - Emoji Stuck Out Tongue Winking Eye - - - - :@ - <img src='/im_chat/static/src/img/scream.png'/> - Emoji Scream - - - - :| - <img src='/im_chat/static/src/img/neutral.png'/> - Emoji Speechless/Neutral face - - - - :p - <img src='/im_chat/static/src/img/yum.png'/> - Emoji Cheeky - - - - :/ - <img src='/im_chat/static/src/img/confused.png'/> - Emoji Confused - - - - :O - <img src='/im_chat/static/src/img/open_mouth.png'/> - Emoji Open Mouth - - - - &lt;3 - <img src='/im_chat/static/src/img/heart.png'/> - Emoji Heart - - - - - :+1 - <img src='/im_chat/static/src/img/thumbsup.png'/> - Emoji Thumbs Up - - - - :-1 - <img src='/im_chat/static/src/img/thumbsdown.png'/> - Emoji Thumbs Down - - - - :kitten - <img src='/im_chat/static/src/img/cat.png'/> - Emoji Kitten - - - - :pinky - <img src='/im_chat/static/src/img/pinky.png'/> - Emoji Pinky - - - - :musti - <img src='/im_chat/static/src/img/musti.png'/> - Emoji Musti - - - - :turban - <img src='/im_chat/static/src/img/man_with_turban.png'/> - Emoji Man With Turban - - - - :troll - <img src='/im_chat/static/src/img/trollface.png'/> - Emoji Troll Face - - - - :boar - <img src='/im_chat/static/src/img/boar.png'/> - Emoji Boar - - - - :poop - <img src='/im_chat/static/src/img/poop.png'/> - Emoji Poop - - - - :cookie - <img src='/im_chat/static/src/img/cookie.png'/> - Emoji Cookie - - - - :postal_horn - <img src='/im_chat/static/src/img/postal_horn.png'/> - Emoji Postal Horn - - - - :ghost - <img src='/im_chat/static/src/img/ghost.png'/> - Emoji Ghost - - - - :beer - <img src='/im_chat/static/src/img/beer.png'/> - Emoji Beer - - - - :man - <img src='/im_chat/static/src/img/man.png'/> - Emoji Man - - - - diff --git a/addons/im_chat/i18n/bg.po b/addons/im_chat/i18n/bg.po deleted file mode 100644 index 41e4d66d85a..00000000000 --- a/addons/im_chat/i18n/bg.po +++ /dev/null @@ -1,197 +0,0 @@ -# Bulgarian translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-11-18 12:00+0000\n" -"Last-Translator: Dimitar Dimitrov \n" -"Language-Team: Bulgarian \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-11-19 06:43+0000\n" -"X-Generator: Launchpad (build 17252)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d Съобщения" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Един потребител може да има само един IM статус." - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Автор" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Отсъстващ(а)" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Приключен" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Дата на създаване" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Създадено от" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Създадено на" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "IM състояние" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Последно Присъствие" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Последно обновено от" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Последно обновено на" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Съобщение" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Съобщения" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Извън линия" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "На линия" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Отвoри" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Кажи нещо..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Сесия" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Потребители" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Посетител" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "Неизвестен" diff --git a/addons/im_chat/i18n/de.po b/addons/im_chat/i18n/de.po deleted file mode 100644 index dde7b9c1578..00000000000 --- a/addons/im_chat/i18n/de.po +++ /dev/null @@ -1,198 +0,0 @@ -# German translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-09-18 13:08+0000\n" -"Last-Translator: Thorsten Vocks (OpenBig.org) \n" -"Language-Team: German \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-09-24 09:14+0000\n" -"X-Generator: Launchpad (build 17196)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d Mitteilungen" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Ein Benutzer kann nur einen eindeutigen Status haben." - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Verfasser" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Abwesend" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Beendet" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Erstellt am" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Erstellt durch" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Erstellt am" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "Zusammen gefaltet" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "Sofortnachrichten-Status" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "Letzte Abfrage" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Letzte Anwesenheit" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Zuletzt aktualisiert von" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Zuletzt aktualisiert am" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Nachricht" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Mitteilungen" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "Meta" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Offline" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "Online" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Offen" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "Verbundene Teilnehmer" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Sag etwas ..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Sitzung" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "Sitzung mit" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "Teilnehmer" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "Typ" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "UUID" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Benutzer" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Besucher" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "unbekannt" diff --git a/addons/im_chat/i18n/es.po b/addons/im_chat/i18n/es.po deleted file mode 100644 index aebf79e2dcf..00000000000 --- a/addons/im_chat/i18n/es.po +++ /dev/null @@ -1,197 +0,0 @@ -# Spanish translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-08-18 14:41+0000\n" -"Last-Translator: Pedro Manuel Baeza \n" -"Language-Team: Spanish \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-09-24 09:14+0000\n" -"X-Generator: Launchpad (build 17196)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d mensajes" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Un usuario sólo puede tener un estado de charla." - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Autor" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Ausente" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Cerrado" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Fecha de creación" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Creado por" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Creado en" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "Replegado" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "Estado del chat" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "Última encuesta" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Última presencia" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Última actualización de" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Última actualización en" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Mensaje" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Mensajes" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "Meta" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Desconectado" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "En línea" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Abrir" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "Usuarios de sesión relacionados" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Diga algo..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Sesión" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "Sesión a" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "Usuarios de sesión" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "Tipo" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "UUID" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Usuarios" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Visitante" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "desconocido" diff --git a/addons/im_chat/i18n/hr.po b/addons/im_chat/i18n/hr.po deleted file mode 100644 index c433b9480b6..00000000000 --- a/addons/im_chat/i18n/hr.po +++ /dev/null @@ -1,197 +0,0 @@ -# Croatian translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-11-15 12:35+0000\n" -"Last-Translator: Davor Bojkić \n" -"Language-Team: Croatian \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-11-16 06:19+0000\n" -"X-Generator: Launchpad (build 17241)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d poruka" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Korisnik može imati samo jedan IM status" - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Autor" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Odsutan" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Zatvoren" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Datum kreiranja" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Kreirao" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Vrijeme kreiranja" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "Omotano" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "IM Status" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Zadnja prijava" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Promijenio" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Vrijeme promjene" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Poruka" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Poruke" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Odspojen" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "Online" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Reci nešto..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "" diff --git a/addons/im_chat/i18n/hu.po b/addons/im_chat/i18n/hu.po deleted file mode 100644 index 86ea7355a4a..00000000000 --- a/addons/im_chat/i18n/hu.po +++ /dev/null @@ -1,197 +0,0 @@ -# Hungarian translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-10-28 07:51+0000\n" -"Last-Translator: krnkris \n" -"Language-Team: Hungarian \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-10-29 07:49+0000\n" -"X-Generator: Launchpad (build 17203)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d Üzenetek" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Egy felhasználónak csak egy üzenőküldési állapota lehet." - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Szerző" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Távollét" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Lezárt" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Létrehozás dátuma" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Készítette" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Létrehozás dátuma" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "Behajtott" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "Azonosító ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "Üzenetküldő-állapot" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "Utolsó szavazás" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Utolsó jelenlét" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Utoljára frissítve, által" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Utoljára frissítve ekkor" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Üzenet" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Üzenetek" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Nem elérhető" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "Elérhető" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Nyitott" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "Szakasz felhasználók környezete" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Mondj valamit..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Munkafázis" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "Munkafázis ehhez" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "Munkafázis felhasználói" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "Típus" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "UUID" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Felhasználók" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Látogató" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "ismeretlen" diff --git a/addons/im_chat/i18n/im_chat.pot b/addons/im_chat/i18n/im_chat.pot deleted file mode 100644 index f19f26fa466..00000000000 --- a/addons/im_chat/i18n/im_chat.pot +++ /dev/null @@ -1,339 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * im_chat -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 9.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-08-25 09:00+0000\n" -"PO-Revision-Date: 2015-08-25 09:00+0000\n" -"Last-Translator: <>\n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat_backend.js:89 -#: code:addons/im_chat/static/src/js/im_chat_common.js:92 -#, python-format -msgid "%d Messages" -msgstr "" - -#. module: im_chat -#: code:addons/im_chat/models/im_chat_session.py:180 -#, python-format -msgid "%s joined the conversation." -msgstr "" - -#. module: im_chat -#: code:addons/im_chat/models/im_chat_session.py:202 -#, python-format -msgid "%s left the conversation." -msgstr "" - -#. module: im_chat -#: model:ir.actions.act_window,help:im_chat.im_chat_shortcode_action -msgid "A shortcode is a keyboard shortcut. For instance, you type #gm and it will be transformed into \"Good Morning\"." -msgstr "" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message_from_id -msgid "Author" -msgstr "" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "" - -#. module: im_chat -#: model:ir.model,name:im_chat.model_im_chat_shortcode -msgid "Canned Response / Shortcode" -msgstr "" - -#. module: im_chat -#: model:ir.model,name:im_chat.model_im_chat_session -msgid "Chat Conversation" -msgstr "" - -#. module: im_chat -#: model:ir.model,name:im_chat.model_im_chat_conversation_state -msgid "Chat Conversation State" -msgstr "" - -#. module: im_chat -#: model:ir.model,name:im_chat.model_im_chat_message -msgid "Chat Message" -msgstr "" - -#. module: im_chat -#: model:ir.actions.act_window,name:im_chat.im_chat_shortcode_action -msgid "Chat Shortcode" -msgstr "" - -#. module: im_chat -#: model:ir.actions.act_window,help:im_chat.im_chat_shortcode_action -msgid "Click to define a new chat shortcode." -msgstr "" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_create_date -msgid "Create Date" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message_create_date -msgid "Create date" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message_create_uid -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence_create_uid -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_create_uid -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_res_users_rel_create_uid -#: model:ir.model.fields,field_description:im_chat.field_im_chat_shortcode_create_uid -msgid "Created by" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence_create_date -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_res_users_rel_create_date -#: model:ir.model.fields,field_description:im_chat.field_im_chat_shortcode_create_date -msgid "Created on" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_shortcode_description -msgid "Description" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message_display_name -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence_display_name -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_display_name -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_res_users_rel_display_name -#: model:ir.model.fields,field_description:im_chat.field_im_chat_shortcode_display_name -msgid "Display Name" -msgstr "" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message_id -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence_id -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_id -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_res_users_rel_id -#: model:ir.model.fields,field_description:im_chat.field_im_chat_shortcode_id -msgid "ID" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence_status -#: model:ir.model.fields,field_description:im_chat.field_res_users_im_status -msgid "IM Status" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message___last_update -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence___last_update -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session___last_update -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_res_users_rel___last_update -#: model:ir.model.fields,field_description:im_chat.field_im_chat_shortcode___last_update -msgid "Last Modified on" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence_last_poll -msgid "Last Poll" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence_last_presence -msgid "Last Presence" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message_write_uid -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence_write_uid -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_res_users_rel_write_uid -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_write_uid -#: model:ir.model.fields,field_description:im_chat.field_im_chat_shortcode_write_uid -msgid "Last Updated by" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message_write_date -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence_write_date -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_res_users_rel_write_date -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_write_date -#: model:ir.model.fields,field_description:im_chat.field_im_chat_shortcode_write_date -msgid "Last Updated on" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message_message -msgid "Message" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message_type -msgid "Message type" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_message_ids -msgid "Messages" -msgstr "" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat_backend.js:47 -#, python-format -msgid "Quit discussion" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_session_res_users_rel -msgid "Relation Session Users" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat_common.js:24 -#, python-format -msgid "Say something..." -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/xml/im_chat.xml:90 -#, python-format -msgid "Search users..." -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_message_to_id -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_res_users_rel_session_id -msgid "Session" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_user_ids -msgid "Session Users" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat_backend.js:52 -#, python-format -msgid "Shortcode" -msgstr "" - -#. module: im_chat -#: model:ir.ui.view,arch_db:im_chat.im_chat_shortcode_view_form -#: model:ir.ui.view,arch_db:im_chat.im_chat_shortcode_view_tree -msgid "Shortcodes" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_shortcode_source -msgid "Shortcut" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat_backend.js:46 -#, python-format -msgid "Shortcuts" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_res_users_rel_state -msgid "Status" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_shortcode_substitution -msgid "Substitution" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,help:im_chat.field_im_chat_shortcode_substitution -msgid "The excaped html code replacing the shortcut" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,help:im_chat.field_im_chat_shortcode_source -msgid "The shortcut which must be replace in the Chat Messages" -msgstr "" - -#. module: im_chat -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_uuid -msgid "UUID" -msgstr "" - -#. module: im_chat -#: model:ir.model,name:im_chat.model_im_chat_presence -msgid "User Chat Presence" -msgstr "" - -#. module: im_chat -#: model:ir.model,name:im_chat.model_res_users -#: model:ir.model.fields,field_description:im_chat.field_im_chat_presence_user_id -#: model:ir.model.fields,field_description:im_chat.field_im_chat_session_res_users_rel_user_id -msgid "Users" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat_common.js:26 -#, python-format -msgid "Visitor" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat_backend.js:70 -#, python-format -msgid "Warning" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat_backend.js:70 -#, python-format -msgid "You are only 2 identified users. Just close the conversation to leave." -msgstr "" - diff --git a/addons/im_chat/i18n/mk.po b/addons/im_chat/i18n/mk.po deleted file mode 100644 index 9df8670027e..00000000000 --- a/addons/im_chat/i18n/mk.po +++ /dev/null @@ -1,197 +0,0 @@ -# Macedonian translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-10-18 10:21+0000\n" -"Last-Translator: Tome Barbov \n" -"Language-Team: Macedonian \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-10-19 07:12+0000\n" -"X-Generator: Launchpad (build 17196)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d Пораки" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Еден корисник може да има само еден IM статус." - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Автор" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Отсутен" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Затворено" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Креирај датум" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Креирано од" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Креирано на" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "IM Статус" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Последно присуство" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Последно ажурирање од" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Последно ажурирање на" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Порака" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Пораки" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "Мета" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Исклучен" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "Вклучен" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Отвори" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Речете нешто..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Сесија" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "Сесија до" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "Корисници на сесија" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "Тип" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "UUID" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Корисници" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Посетител" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "непознато" diff --git a/addons/im_chat/i18n/nl.po b/addons/im_chat/i18n/nl.po deleted file mode 100644 index fc48acfc54e..00000000000 --- a/addons/im_chat/i18n/nl.po +++ /dev/null @@ -1,197 +0,0 @@ -# Dutch translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-08-15 16:07+0000\n" -"Last-Translator: Mario Gielissen (Openworx) \n" -"Language-Team: Dutch \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-09-24 09:14+0000\n" -"X-Generator: Launchpad (build 17196)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d Berichten" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Een gebruiker kan maar een IM status hebben." - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Auteur" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Afwezig" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Gesloten" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Aanmaakdatum" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Aangemaakt door" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Aangemaakt op" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "Gevouwen" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "IM status" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "Laatste pol" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Laatst aanwezig" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Laatste bijgewerkt door" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Laatst bijgewerkt op" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Bericht" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Berichten" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "Meta" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Offline" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "Online" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Open" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "Relatie gebruikers sessie" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Zeg iets ..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Sessie" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "Sessie naar" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "Gebruikers sessie" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "Type" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "UUID" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Gebruikers" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Bezoeker" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "onbekend" diff --git a/addons/im_chat/i18n/pl.po b/addons/im_chat/i18n/pl.po deleted file mode 100644 index 96c7c3e098a..00000000000 --- a/addons/im_chat/i18n/pl.po +++ /dev/null @@ -1,198 +0,0 @@ -# Polish translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-08-27 21:10+0000\n" -"Last-Translator: Monika Grzelak (OpenGLOBE.pl) " -"\n" -"Language-Team: Polish \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-09-24 09:14+0000\n" -"X-Generator: Launchpad (build 17196)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d Wiadomości" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Użytkownik może mieć tylko jeden status komunikatora." - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Autor" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Zaraz wracam" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Zamknięte" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Data utworzenia" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Utworzone przez" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Data utworzenia" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "Zwinięte" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "Status komunikatora" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "Ostatnia ankieta" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Ostatnia obecność" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Ostatnio modyfikowane przez" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Data ostatniej modyfikacji" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Wiadomość" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Wiadomości" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Niedostępny" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "Dostępny" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Otwórz" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "Uczestnicy sesji relacyjnej" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Powiedz coś..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Sesja" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "Sesja do" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "Użytkownicy sesji" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "Typ" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Użytkownicy" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Gość" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "nieznane" diff --git a/addons/im_chat/i18n/pt.po b/addons/im_chat/i18n/pt.po deleted file mode 100644 index 614a9d49cbb..00000000000 --- a/addons/im_chat/i18n/pt.po +++ /dev/null @@ -1,197 +0,0 @@ -# Portuguese translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-09-17 17:42+0000\n" -"Last-Translator: Opencloud - PT \n" -"Language-Team: Portuguese \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-09-24 09:14+0000\n" -"X-Generator: Launchpad (build 17196)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d Mensagens" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Um utilizador pode apenas ter um estado IM." - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Autor" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Ausente" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Fechado" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Data de Criação" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Criado por" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Criado em" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "Dobrado" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "Estado IM" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "Última Votação" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Última Presença" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Última Actualização por" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Última Actualização em" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Mensagem" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Mensagens" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "Meta" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Desligado" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "Ligado" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Abrir" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "Utilizadores da Sessão Relacionados" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Diga algo..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Sessão" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "Sessão a" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "Utilizadores da Sessão" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "Tipo" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "UUID" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Utilizadores" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Visitante" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "desconhecido" diff --git a/addons/im_chat/i18n/pt_BR.po b/addons/im_chat/i18n/pt_BR.po deleted file mode 100644 index cd0a1ff72b5..00000000000 --- a/addons/im_chat/i18n/pt_BR.po +++ /dev/null @@ -1,198 +0,0 @@ -# Brazilian Portuguese translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-09-06 19:08+0000\n" -"Last-Translator: Fábio Martinelli - http://zupy.com.br " -"\n" -"Language-Team: Brazilian Portuguese \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-09-24 09:14+0000\n" -"X-Generator: Launchpad (build 17196)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d Mensagens" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Um usuário pode ter apenas um status" - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Autor" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Ausente" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Fechado" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Data de criação" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Criado por" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Criado em" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "Dobrado" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "Status do mensageiro" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "Última votação" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Última presença" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Última atualização por" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Última atualização em" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Mensagem" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Mensagens" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "Meta" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Desconectado" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "Conectado" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Aberto" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "Relação Sessões Usuários" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Diga alguma coisa..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Sessão" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "Sessão para" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "Seção de usuários" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "Digite" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "UUID" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Usuários" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Visitante" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "desconhecido" diff --git a/addons/im_chat/i18n/ro.po b/addons/im_chat/i18n/ro.po deleted file mode 100644 index ffb78e7b545..00000000000 --- a/addons/im_chat/i18n/ro.po +++ /dev/null @@ -1,197 +0,0 @@ -# Romanian translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-10-16 04:31+0000\n" -"Last-Translator: Dorin \n" -"Language-Team: Romanian \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-10-17 06:27+0000\n" -"X-Generator: Launchpad (build 17196)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d Mesaje" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "" - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Autor" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Absent" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Închis" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Data creării" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Creat de" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Creat la" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "Îndoită" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "Stare mesagerie instant" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Ultima actualizare făcută de" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Mesaj" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Mesaje" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "Meta" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Deconectat" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "Activ" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Deschide" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "" - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Sesiune" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "Tip" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "UUID" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Utilizatori" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Vizitator" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "necunoscut" diff --git a/addons/im_chat/i18n/tr.po b/addons/im_chat/i18n/tr.po deleted file mode 100644 index 9c3f92ccf02..00000000000 --- a/addons/im_chat/i18n/tr.po +++ /dev/null @@ -1,197 +0,0 @@ -# Turkish translation for openobject-addons -# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2014. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2014-09-23 16:27+0000\n" -"PO-Revision-Date: 2014-10-11 07:08+0000\n" -"Last-Translator: Ayhan KIZILTAN \n" -"Language-Team: Turkish \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-10-12 07:19+0000\n" -"X-Generator: Launchpad (build 17196)\n" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:88 -#, python-format -msgid "%d Messages" -msgstr "%d Mesaj" - -#. module: im_chat -#: sql_constraint:im_chat.presence:0 -msgid "A user can only have one IM status." -msgstr "Bir kullanıcı yalnozca bir IM durumunda olabilir." - -#. module: im_chat -#: field:im_chat.message,from_id:0 -msgid "Author" -msgstr "Yazan" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Away" -msgstr "Dışarıda" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Closed" -msgstr "Kapalı" - -#. module: im_chat -#: field:im_chat.message,create_date:0 -msgid "Create Date" -msgstr "Oluşturma Tarihi" - -#. module: im_chat -#: field:im_chat.conversation_state,create_uid:0 -#: field:im_chat.message,create_uid:0 -#: field:im_chat.presence,create_uid:0 -#: field:im_chat.session,create_uid:0 -msgid "Created by" -msgstr "Oluşturan" - -#. module: im_chat -#: field:im_chat.conversation_state,create_date:0 -#: field:im_chat.presence,create_date:0 -#: field:im_chat.session,create_date:0 -msgid "Created on" -msgstr "Oluşturulma" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Folded" -msgstr "Katlanmış" - -#. module: im_chat -#: field:im_chat.conversation_state,id:0 -#: field:im_chat.message,id:0 -#: field:im_chat.presence,id:0 -#: field:im_chat.session,id:0 -msgid "ID" -msgstr "ID" - -#. module: im_chat -#: field:im_chat.presence,status:0 -#: field:res.users,im_status:0 -msgid "IM Status" -msgstr "Anlık İleti Durumu" - -#. module: im_chat -#: field:im_chat.presence,last_poll:0 -msgid "Last Poll" -msgstr "Son Anket" - -#. module: im_chat -#: field:im_chat.presence,last_presence:0 -msgid "Last Presence" -msgstr "Son Durum" - -#. module: im_chat -#: field:im_chat.conversation_state,write_uid:0 -#: field:im_chat.message,write_uid:0 -#: field:im_chat.presence,write_uid:0 -#: field:im_chat.session,write_uid:0 -msgid "Last Updated by" -msgstr "Son Güncelleyen" - -#. module: im_chat -#: field:im_chat.conversation_state,write_date:0 -#: field:im_chat.message,write_date:0 -#: field:im_chat.presence,write_date:0 -#: field:im_chat.session,write_date:0 -msgid "Last Updated on" -msgstr "Son Güncelleme" - -#. module: im_chat -#: field:im_chat.message,message:0 -#: selection:im_chat.message,type:0 -msgid "Message" -msgstr "Mesaj" - -#. module: im_chat -#: field:im_chat.session,message_ids:0 -msgid "Messages" -msgstr "Mesajlar" - -#. module: im_chat -#: selection:im_chat.message,type:0 -msgid "Meta" -msgstr "Meta" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Offline" -msgstr "Çevrimdışı" - -#. module: im_chat -#: selection:im_chat.presence,status:0 -msgid "Online" -msgstr "Çevrimiçi" - -#. module: im_chat -#: selection:im_chat.conversation_state,state:0 -msgid "Open" -msgstr "Aç" - -#. module: im_chat -#: field:im_chat.session,session_res_users_rel:0 -msgid "Relation Session Users" -msgstr "İlgili Oturum Kullanıcısı" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:18 -#, python-format -msgid "Say something..." -msgstr "Bir şey söyle..." - -#. module: im_chat -#: field:im_chat.conversation_state,session_id:0 -msgid "Session" -msgstr "Oturum" - -#. module: im_chat -#: field:im_chat.message,to_id:0 -msgid "Session To" -msgstr "Oturum Açılan" - -#. module: im_chat -#: field:im_chat.session,user_ids:0 -msgid "Session Users" -msgstr "Oturum Kullanıcısı" - -#. module: im_chat -#: field:im_chat.message,type:0 -msgid "Type" -msgstr "Türü" - -#. module: im_chat -#: field:im_chat.session,uuid:0 -msgid "UUID" -msgstr "UUID" - -#. module: im_chat -#: field:im_chat.conversation_state,user_id:0 -#: field:im_chat.presence,user_id:0 -#: model:ir.model,name:im_chat.model_res_users -msgid "Users" -msgstr "Kullanıcılar" - -#. module: im_chat -#. openerp-web -#: code:addons/im_chat/static/src/js/im_chat.js:20 -#, python-format -msgid "Visitor" -msgstr "Ziyaretçi" - -#. module: im_chat -#: field:im_chat.conversation_state,state:0 -msgid "unknown" -msgstr "bilinmeyen" diff --git a/addons/im_chat/models/__init__.py b/addons/im_chat/models/__init__.py deleted file mode 100644 index 1ee6a543eb9..00000000000 --- a/addons/im_chat/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -import im_chat_session -import res_users diff --git a/addons/im_chat/models/im_chat_session.py b/addons/im_chat/models/im_chat_session.py deleted file mode 100644 index 0d3f11c5d14..00000000000 --- a/addons/im_chat/models/im_chat_session.py +++ /dev/null @@ -1,462 +0,0 @@ -# -*- coding: utf-8 -*- -import cgi -import datetime -import random -import re -import time -import uuid - -from openerp import tools, _ -from openerp import api, fields, models -from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT - -from openerp.addons.bus.models.bus import TIMEOUT - - -DISCONNECTION_TIMER = TIMEOUT + 5 -AWAY_TIMER = 600 # 10 minutes - - - -class ImChatConversationState(models.Model): - """ User Session State - Adds a state on the m2m between user and session. - """ - - _name = 'im_chat.conversation_state' - _description = 'Chat Conversation State' - _table = 'im_chat_session_res_users_rel' - - state = fields.Selection([('open', 'Open'), ('folded', 'Folded'), ('closed', 'Closed')], string='Status', default='open') - session_id = fields.Many2one('im_chat.session', string='Session', required=True, ondelete='cascade') - user_id = fields.Many2one('res.users', string='Users', required=True, ondelete='cascade') - - - -class ImChatSession(models.Model): - """ Session - Reprensenting a conversation between users. It manages anonymous user for method starting - with 'session_'. - """ - - _name = 'im_chat.session' - _rec_name = 'uuid' - _description = 'Chat Conversation' - _order = 'id desc' - - def _default_uuid(self): - return uuid.uuid4() - - uuid = fields.Char(string='UUID', index=True, default=_default_uuid) - message_ids = fields.One2many('im_chat.message', 'to_id', string='Messages') - session_res_users_rel = fields.One2many('im_chat.conversation_state', 'session_id', string='Relation Session Users') - user_ids = fields.Many2many('res.users', 'im_chat_session_res_users_rel', 'session_id', 'user_id', string='Session Users') - - # -------------------------- - # Session Methods - # -------------------------- - @api.multi - def session_info(self): - """ Get the header of the current session - :returns the values of the session - :rtype : dict - """ - self.ensure_one() - info = { - 'uuid': self.uuid, - 'users': self.session_user_info(), - 'state': 'open', - } - # add user session state, if available and if user is logged - if self._uid: - domain = [('user_id', '=', self._uid), ('session_id', 'in', self.ids)] - session_state = self.env['im_chat.conversation_state'].search(domain, limit=1) - if session_state: - info['state'] = session_state.state - return info - - @api.multi - def session_user_info(self): - """ Get the user infos for all the identified user in the session - :returns a list of user infos - :rtype : list(dict) - """ - self.ensure_one() - return self.env['res.users'].browse(self.user_ids.ids).read(['id', 'name', 'im_status']) - - @api.model - def session_user_image(self, uuid, user_id): - """ Get the avatar of a user in the given session - :param uuid : the uuid of the session - :param user_id : the user identifier - """ - # get the session - image_b64 = False - if user_id: - session = self.env["im_chat.session"].search([('uuid', '=', uuid)], limit=1) - if session and session.sudo(user_id).is_in_session(): - image_b64 = self.env["res.users"].sudo().browse(user_id).image_small - # set default image if not found - if not image_b64: - image_b64 = 'R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' - return image_b64 - - @api.model - def session_get(self, user_to): - """ Get the canonical session between 2 users, create it if needed. - To reuse an old session, this one must be private, and contains only 2 users. - :param user_to : the identifier of the user - :returns a session header, or False if the user_to was False - :rtype : dict - """ - if user_to: - session = self.search([('user_ids', 'in', user_to), ('user_ids', 'in', [self._uid])], limit=1) - if not (session and len(session.user_ids) == 2 and session.is_private()): - session = self.create({'user_ids': [(6, 0, (user_to, self._uid))]}) - return session.session_info() - - # -------------------------- - # Utils Methods - # -------------------------- - @api.multi - def is_private(self): - """ Return true if the session is private between identified users (no external messages). - The only way to do that is to check if there is a message without author (from_id is False). - """ - self.ensure_one() - message_ids = self.env["im_chat.message"].search([('to_id', 'in', self.ids), ('from_id', '=', None)]) - return len(message_ids) == 0 - - @api.multi - def is_in_session(self): - """ Return True if the current user is in the user_ids of the session. False otherwise. - Note : the user need to be logged - """ - self.ensure_one() - user_id = self._uid - return user_id and user_id in [u.id for u in self.user_ids] - - @api.model - def update_state(self, uuid, state=None): - """ Update the fold_state of the given session. In order to syncronize web browser - tabs, the change will be broadcast to himself (the current user channel). - Note : the user need to be logged - :param status : the new status of the session for the current user. - """ - domain = [('user_id', '=', self._uid), ('session_id.uuid', '=', uuid)] - for session_state in self.env['im_chat.conversation_state'].search(domain): - if not state: - state = session_state.state - if session_state.state == 'open': - state = 'folded' - else: - state = 'open' - session_state.write({'state': state}) - self.env['bus.bus'].sendone((self._cr.dbname, 'im_chat.session', self._uid), session_state.session_id.session_info()) - - # -------------------------- - # User Actions - # -------------------------- - @api.model - def add_user(self, uuid): - """ Add the current user to the session, and broadcast the meta message. - Note : the user need to be logged - :param uuid : the uuid of the session - """ - session = self.search([('uuid', '=', uuid)], limit=1) - if user_id not in [u.id for u in session.user_ids]: - session.write({'user_ids': [(4, self._uid)]}) - # notify the all the channel users and anonymous channel - notifications = [] - for user in session.user_ids: - info = session.sudo(user.id).session_info() - notifications.append([(self._cr.dbname, 'im_chat.session', user.id), info]) - # anonymous are not notified when a new user is added : cannot execute session_info as uid = None - info = session.sudo().session_info() - notifications.append([session.uuid, info]) - self.env['bus.bus'].sendmany(notifications) - # send a message to the conversation - user = self.env['res.users'].browse(self._uid) - message = _("%s joined the conversation.") % (user.name,) - self.env["im_chat.message"].send_message(session.uuid, "meta", message) - - @api.model - def quit_user(self, uuid): - """ Remove the current user from the given session. - Note : the user need to be logged - """ - session = self.search([('uuid', '=', uuid)], limit=1) - if self._uid in [u.id for u in session.user_ids] and len(session.user_ids) > 2: - return session._remove_user() - return False - - @api.multi - def _remove_user(self): - """ Private implementation of removing the current user from the current session, - and broadcast the meta message. - Note : the user need to be logged - """ - self.ensure_one() - # send a message to the conversation - user = self.env['res.users'].browse(self._uid) - message = _("%s left the conversation.") % (user.name,) - self.env["im_chat.message"].send_message(self.uuid, "meta", message) - # close his session state - self.update_state(self.uuid, 'closed') - # remove the user from session - self.write({"user_ids": [(3, self._uid)]}) - # notify the all the channel users and anonymous channel - notifications = [] - for user in self.user_ids: - info = self.sudo(user.id).session_info() - notifications.append([(self._cr.dbname, 'im_chat.session', user.id), info]) - # anonymous are not notified when a new user left : cannot execute session_info as uid = None - info = self.sudo().session_info() - notifications.append([self.uuid, info]) - self.env['bus.bus'].sendmany(notifications) - return True - - - -class ImChatMessage(models.Model): - """ Session Messsages - The chat provide 2 message type : 'message' (normal user message) - or 'meta' (message relative to changes about sessions) - For anonymous message, the from_id is False. - Messages are sent to a session not to users. - """ - - _name = 'im_chat.message' - _description = 'Chat Message' - _order = 'create_date asc' - - @api.model - def _default_type(self): - return [('message', 'Message'), ('meta', 'Meta')] - - create_date = fields.Datetime(string="Create date", required=True, index=True) - from_id = fields.Many2one('res.users', 'Author') - to_id = fields.Many2one('im_chat.session', 'Session', required=True, index=True) - type = fields.Selection('_default_type', 'Message type', default='message') - message = fields.Char('Message') - - @api.model - def _escape_keep_url(self, message): - """ Escape the message and transform the url into clickable link - :param message : the message to escape url and transform them into clickable link - :returns the escaped message - """ - safe_message = "" - first = 0 - last = 0 - for m in re.finditer('(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?', message): - last = m.start() - safe_message += cgi.escape(message[first:last]) - safe_message += '%s' % (cgi.escape(m.group(0)), m.group(0)) - first = m.end() - last = m.end() - safe_message += cgi.escape(message[last:]) - return safe_message - - @api.model - def get_init_notifications(self): - """ Get unread messages and old messages received less than AWAY_TIMER - ago and the session_info for open or folded window - Note : the user need to be logged - """ - # get the message since the AWAY_TIMER - threshold = datetime.datetime.now() - datetime.timedelta(seconds=AWAY_TIMER) - threshold = threshold.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - domain = [('to_id.user_ids', 'in', [self._uid]), ('create_date', '>', threshold)] - - # get the message since the last poll of the user - presence = self.env['im_chat.presence'].search([('user_id', '=', self._uid)], limit=1) - if presence: - domain.append(('create_date', '>', presence.last_poll)) - messages = self.search_read(domain, ['from_id', 'to_id', 'create_date', 'type', 'message'], order='id asc') - - # get the session of the messages and the not-closed ones - session_ids = [m['to_id'][0] for m in messages] - domain = [('user_id', '=', self._uid), '|', ('state', '!=', 'closed'), ('session_id', 'in', session_ids)] - session_states = self.env['im_chat.conversation_state'].search(domain) - # re-open the session where a message have been recieved recently - session_states.filtered(lambda r: r.state == 'closed').write({'state': 'folded'}) - - # create the notifications (session infos first, then messages) - notifications = [] - for state in session_states: - session_infos = state.session_id.session_info() - notifications.append([(self._cr.dbname, 'im_chat.session', self._uid), session_infos]) - for message in messages: - notifications.append([(self._cr.dbname, 'im_chat.session', self._uid), message]) - return notifications - - - @api.model - def send_message(self, from_uid, uuid, message_content, message_type='message'): - """ Post and broadcast a message for the given session, return the message id - :param from_uid : user id of the author of the message - :param uuid : the uuid of the session - :param message_content : the content of the message - :param message_type : the type of the message - :returns the identifier of the new created message - :rtype : integer - """ - session = self.env['im_chat.session'].search([('uuid', '=', uuid)], limit=1) - # escape the new message - message_content = self._escape_keep_url(message_content) - message_content = self.env['im_chat.shortcode'].replace_shortcode(message_content) - # create the chat message - values = { - "from_id": from_uid, - "to_id": session.id, - "type": message_type, - "message": message_content, - } - message = self.create(values) - # broadcast the message - data = message.read(['from_id', 'to_id', 'create_date', 'type', 'message'])[0] - notifications = [] - notifications.append([uuid, data]) - for user in session.sudo().user_ids: # sudo required for portal user - notifications.append([(self._cr.dbname, 'im_chat.session', user.id), data]) - self.env['bus.bus'].sendmany(notifications) - return message.id - - @api.model - def fetch_message(self, uuid, last_id=False, limit=20): - """ Return messages for the given session uuid. If the session is public, fetch_message - as 'sudo', else (private session) fetch them as the current user. The security rule - 'message_rule_1' will check the access right. - :param uuid : uuid of the session to fetch messages - :param last_id : last message id to start the research - :param limit : maximum number of messages to fetch - :returns list of messages values - :rtype : list(dict) - """ - session = self.env['im_chat.session'].search([('uuid', '=', uuid)], limit=1) - if session and session.is_in_session(): - domain = [("to_id.uuid", "=", uuid)] - if last_id: - domain.append(("id", "<", last_id)) - return self.search_read(domain, ['id', 'create_date', 'to_id', 'from_id', 'type', 'message'], limit=limit, order="id desc") - return False - - - -class ImChatShortcode(models.Model): - """ Shortcode - Canned Responses, allowing the user to defined shortcuts in its chat message. - These shortcode are globals and are available for every user. Smiley use this mecanism. - """ - - _name = 'im_chat.shortcode' - _description = 'Canned Response / Shortcode' - - source = fields.Char('Shortcut', required=True, index=True, help="The shortcut which must be replace in the Chat Messages") - substitution = fields.Char('Substitution', required=True, index=True, help="The excaped html code replacing the shortcut") - description = fields.Char('Description') - - @api.model - def create(self, values): - if values.get('substitution'): - values['substitution'] = self._sanitize_shorcode(values['substitution']) - return super(ImChatShortcode, self).create(values) - - @api.multi - def write(self, values): - if values.get('substitution'): - values['substitution'] = self._sanitize_shorcode(values['substitution']) - return super(ImChatShortcode, self).write(values) - - def _sanitize_shorcode(self, substitution): - """ Sanitize the shortcode substitution : - - HTML substitution : only allow the img tag (smiley) - - escape other substitutions to avoid XSS - """ - is_img_tag = re.match(r'''^$''', substitution, re.M|re.I) - if is_img_tag: - return substitution - return cgi.escape(substitution) - - @api.model - def replace_shortcode(self, message): - for shortcode in self.search([]): - regex = '(?:^|\s)(%s)(?:\s|$)' % re.escape(shortcode.source) - message = re.sub(regex, " " + shortcode.substitution + " ", message) - return message - - - -class ImChatPresence(models.Model): - """ User Chat Presence - Its status is 'online', 'away' or 'offline'. This model should be a one2one, but is not - attached to res_users to avoid database concurrence errors. Since the 'update' method is executed - at each poll, if the user have multiple opened tabs, concurrence errors can happend, but are 'muted-logged'. - """ - - _name = 'im_chat.presence' - _description = 'User Chat Presence' - - _sql_constraints = [('im_chat_user_status_unique', 'unique(user_id)', 'A user can only have one IM status.')] - - user_id = fields.Many2one('res.users', 'Users', required=True, index=True, ondelete='cascade') - last_poll = fields.Datetime('Last Poll', default=lambda self: fields.Datetime.now()) - last_presence = fields.Datetime('Last Presence', default=lambda self: fields.Datetime.now()) - status = fields.Selection([('online', 'Online'), ('away', 'Away'), ('offline', 'Offline')], 'IM Status', default='offline') - - - @api.model - def update(self, user_presence=True): - """ Register the given presence of the current user, and trigger a im_status change if necessary. - The status will not be written or sent if not necessary. - :param user_presence : True, if the user (self._uid) is still detected using its browser. - :type user_presence : boolean - """ - presence = self.search([('user_id', '=', self._uid)], limit=1) - # set the default values - send_notification = True - values = { - 'last_poll': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'status' : presence and presence.status or 'offline' - } - # update the user or a create a new one - if not presence: # create a new presence for the user - values['status'] = 'online' - values['user_id'] = self._uid - self.create(values) - else: # write the user presence if necessary - if user_presence: - values['last_presence'] = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - values['status'] = 'online' - else: - threshold = datetime.datetime.now() - datetime.timedelta(seconds=AWAY_TIMER) - if datetime.datetime.strptime(presence.last_presence, DEFAULT_SERVER_DATETIME_FORMAT) < threshold: - values['status'] = 'away' - send_notification = presence.status != values['status'] - # write only if the last_poll is passed TIMEOUT, or if the status has changed - delta = datetime.datetime.utcnow() - datetime.datetime.strptime(presence.last_poll, DEFAULT_SERVER_DATETIME_FORMAT) - if delta > datetime.timedelta(seconds=TIMEOUT) or send_notification: - # Hide transaction serialization errors, which can be ignored, the presence update is not essential - with tools.mute_logger('openerp.sql_db'): - presence.write(values) - # avoid TransactionRollbackError - self.env.cr.commit() # TODO : check if still necessary - # notify if the status has changed - if send_notification: # TODO : add user_id to the channel tuple to allow using user_watch in controller presence - self.env['bus.bus'].sendone((self._cr.dbname, 'im_chat.presence'), {'id': self._uid, 'im_status': values['status']}) - # gc : disconnect the users having a too old last_poll. 1 on 100 chance to do it. - if random.random() < 0.01: - self.check_users_disconnection() - return True - - @api.model - def check_users_disconnection(self): - """ Disconnect the users having a too old last_poll """ - limit_date = (datetime.datetime.utcnow() - datetime.timedelta(0, DISCONNECTION_TIMER)).strftime(DEFAULT_SERVER_DATETIME_FORMAT) - presences = self.search([('last_poll', '<', limit_date), ('status', '!=', 'offline')]) - presences.write({'status': 'offline'}) - notifications = [] - for presence in presences: - notifications.append([(self._cr.dbname, 'im_chat.presence'), {'id': presence.user_id.id, 'im_status': presence.status}]) - self.env['bus.bus'].sendmany(notifications) diff --git a/addons/im_chat/security/im_chat_session_security.xml b/addons/im_chat/security/im_chat_session_security.xml deleted file mode 100644 index a70fe1835b6..00000000000 --- a/addons/im_chat/security/im_chat_session_security.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - Can only read messages that you sent or messages sent to you - - - [('to_id.user_ids', 'in', [user.id])] - - - - - - - - Can only modify your session - - - [('user_ids', 'in', [user.id])] - - - - - - - - Can only modify your own session relations - - - [('user_id', '=', user.id)] - - - - - - - diff --git a/addons/im_chat/security/ir.model.access.csv b/addons/im_chat/security/ir.model.access.csv deleted file mode 100644 index 7d09c73050e..00000000000 --- a/addons/im_chat/security/ir.model.access.csv +++ /dev/null @@ -1,11 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_im_chat_message,im_chat.message,model_im_chat_message,base.group_user,1,0,1,0 -access_im_chat_session,im_chat.session,model_im_chat_session,base.group_user,1,1,1,0 -access_im_chat_conversation_state,im_chat.conversation_state,model_im_chat_conversation_state,base.group_user,1,1,1,0 -access_im_chat_presence,im_chat.presence,model_im_chat_presence,base.group_user,1,1,1,1 -access_im_chat_shortcode,im_chat.shortcode,model_im_chat_shortcode,base.group_user,1,1,1,1 -access_im_chat_message_portal,im_chat.message,model_im_chat_message,base.group_portal,1,0,1,0 -access_im_chat_session_portal,im_chat.session,model_im_chat_session,base.group_portal,1,1,1,0 -access_im_chat_conversation_state_portal,im_chat.conversation_state,model_im_chat_conversation_state,base.group_portal,1,1,1,0 -access_im_chat_presence_portal,im_chat.presence,model_im_chat_presence,base.group_portal,1,1,1,1 -access_im_chat_shortcode_portal,im_chat.shortcode,model_im_chat_shortcode,base.group_portal,1,0,0,0 \ No newline at end of file diff --git a/addons/im_chat/static/description/icon.png b/addons/im_chat/static/description/icon.png deleted file mode 100644 index 4fe0a5ddfbc..00000000000 Binary files a/addons/im_chat/static/description/icon.png and /dev/null differ diff --git a/addons/im_chat/static/src/audio/purr.mp3 b/addons/im_chat/static/src/audio/purr.mp3 deleted file mode 100644 index 849d303c3b1..00000000000 Binary files a/addons/im_chat/static/src/audio/purr.mp3 and /dev/null differ diff --git a/addons/im_chat/static/src/audio/purr.ogg b/addons/im_chat/static/src/audio/purr.ogg deleted file mode 100644 index 2ddc77537f1..00000000000 Binary files a/addons/im_chat/static/src/audio/purr.ogg and /dev/null differ diff --git a/addons/im_chat/static/src/css/im_chat_backend.css b/addons/im_chat/static/src/css/im_chat_backend.css deleted file mode 100644 index d01577eb56a..00000000000 --- a/addons/im_chat/static/src/css/im_chat_backend.css +++ /dev/null @@ -1,114 +0,0 @@ -.oe_im { - position: fixed; - background-color: #eaeaea; - width: 250px; - border-left: 1px solid #AEB9BD; - z-index: 500; - -webkit-box-shadow: -2px 0px 5px 0px rgba(50, 50, 50, 0.25); - -moz-box-shadow: -2px 0px 5px 0px rgba(50, 50, 50, 0.25); - box-shadow: -2px 0px 5px 0px rgba(50, 50, 50, 0.25); -} - -@media print { - .oe_im, .oe_im_chatview { - visibility: hidden; - } -} -/* button */ -.oe_systray #oe_topbar_imbutton_icon { - color: white; -} -.oe_topbar_item.oe_topbar_imbutton .fa { - position: relative; - top: -8px; -} - -/* search stuff */ -.oe_im_frame_header { - position: relative; - padding: 16px; -} -.oe_im_frame_header .oe_im_searchbox { - width: 200px; - padding: 1px 21px 1px 19px; - font-size: 13px; - border: none; - border-bottom: 1px solid #c6c6c6; - background: none; - margin-left: 20px; - height: 38px; -} -.oe_im_frame_header .oe_im_searchbox:focus { - outline: none; -} -.oe_im_frame_header .oe_im_search_icon { - position: absolute; - color: #888; - top: 28px; - left: 8px; - font-size: 16px; - font-weight: 300 !important; -} -.oe_im_frame_header .oe_im_search_clear { - display: none; - position: absolute; - right: 11px; - top: 4px; - font-size: 26px; - color: #b6b6b6; - cursor: pointer; -} -.oe_im_frame_header .oe_im_search_clear:hover { - color: #888; -} - -/* users */ - -.oe_im_users { - padding-bottom: 38px; - height: 100%; - overflow-y: scroll; -} -.oe_im_user { - position: relative; - padding: 2px 8px; - cursor: pointer; - font-size: 13px; - margin-bottom: 10px; -} -.oe_im_user:hover { - background: lightGrey; -} -.oe_im_user_clip { - display: inline-block; - width: 26px; - height: 26px; - margin-right: 4px; - -moz-box-shadow: 0 0 2px 1px rgba(0,0,0,0.25); - -webkit-box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25); - box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25); -} -.oe_im_user_avatar { - width: 26px; - height: auto; -} -.oe_im_user_name { - width: 162px; - line-height: 26px; - padding-right: 15px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - position: relative; -} - -.oe_im_user_online { - display: none; - position: absolute; - top: 9.5px; - right: 11px; - width: 11px; - height: 11px; - vertical-align: middle; - border: 0; -} diff --git a/addons/im_chat/static/src/css/im_chat_common.css b/addons/im_chat/static/src/css/im_chat_common.css deleted file mode 100644 index 1ad2981eb41..00000000000 --- a/addons/im_chat/static/src/css/im_chat_common.css +++ /dev/null @@ -1,259 +0,0 @@ - - - -.openerp_style { /* base style of openerp */ - font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif; - color: #4c4c4c; - font-size: 13px; - background: white; -} - -/* button */ - -.oe_chat_button { - 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; -} - -/* conversations */ - -.oe_im_chatview { - position: fixed; - overflow: hidden; - margin-bottom: 5px; - margin-right: 6px; - background-color: #fff; - border: 1px solid #DEDEDE; - width: 240px; - z-index:500; -} -@media screen and (max-width:360px) { - .oe_im_chatview { - width: 100%; - margin-right: 0; - } - .oe_im_chatview .oe_im_chatview_content { - width: 100% !important; - } - .oe_im_chatview .oe_im_chatview_input { - width: 100% !important; - } -} -.oe_im_chatview .oe_im_chatview_disconnected { - display:none; - z-index: 100; - width: 100%; - background: #E8EBEF; - padding: 5px; - font-size: 11px; - color: #999; - line-height: 14px; - height: 28px; - overflow: hidden; -} -.oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_disconnected { - display: block; -} - - - -/* ####### Chat View : Header ####### */ -.oe_im_chatview_header { - padding: 3px 6px 2px; - background: #DEDEDE; - background: -moz-linear-gradient(#FCFCFC, #DEDEDE); - background: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#DEDEDE)); - border-radius: 3px 3px 0 0; - -moz-border-radius: 3px 3px 0 0; - -webkit-border-radius: 3px 3px 0 0; - border-bottom: 1px solid #AEB9BD; - cursor: pointer; -} - -.oe_im_chatview_header .header_name{ - max-width: 75%; - word-wrap: break-word; - display: inline-block; - height: 15px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.oe_im_chatview_header .right_buttons { - float: right; -} -.oe_im_chatview_header .right_buttons > * { - display: inline-block; - border: 0; - cursor: pointer; - color: gray ; - background: transparent; - font-size: 16px; - line-height: 16px; - margin-left: 5px; - -webkit-appearance: none; -} - -.oe_im_chatview_header .right_buttons .dropdown-menu{ - right: 0; - left: auto; -} -.oe_im_chatview_header .right_buttons .dropdown-menu > li{ - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 1.428571429; - color: #4c4c4c; - white-space: nowrap; -} - - - -.oe_im_chatview .oe_im_chatview_content { - overflow: auto; - height: 287px; - width: 240px; -} -.oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_content { - height: 249px; -} -.oe_im_chatview .oe_im_chatview_footer { - position: relative; - padding: 3px; - border-top: 1px solid #AEB9BD; - background: #DEDEDE; - background: -moz-linear-gradient(#FCFCFC, #DEDEDE); - background: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#DEDEDE)); - -moz-border-radius: 0 0 3px 3px; - -webkit-border-radius: 0 0 3px 3px; - border-radius: 0 0 3px 3px; -} -.oe_im_chatview .oe_im_chatview_input { - width: 222px; - font-family: Lato, Helvetica, sans-serif; - font-size: 13px; - color: #333; - padding: 1px 5px; - border: 1px solid #AEB9BD; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - -moz-box-shadow: inset 0 1px 4px rgba(0,0,0,0.2); - -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2); - box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2); -} -.oe_im_chatview .oe_im_chatview_message_bubble { - background: white; - position: relative; - min-height: 32px; - padding: 3px; - margin: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; -} - -/* Smiley size */ -.oe_im_chatview .oe_im_chatview_bubble_item img{ - width: 18px; -} - -.oe_im_chatview .oe_im_chatview_technical_bubble { - background: #D8D8D8; - position: relative; - min-height: 32px; - padding: 3px; - margin: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - - font-style:italic; -} - -.oe_im_chatview .oe_im_chatview_clip { - position: relative; - float: left; - width: 26px; - height: 26px; - margin-right: 4px; - -moz-box-shadow: 0 0 2px 1px rgba(0,0,0,0.25); - -webkit-box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25); - box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25); -} -.oe_im_chatview .oe_im_chatview_avatar { - float: left; - width: 26px; - height: auto; - clip: rect(0, 26px, 26px, 0); - max-width: 100%; - width: auto 9; - height: auto; - vertical-align: middle; - border: 0; - -ms-interpolation-mode: bicubic; -} -.oe_im_chatview .oe_im_chatview_time { - position: absolute; - right: 0px; - top: 0px; - margin: 3px; - text-align: right; - line-height: 13px; - font-size: 11px; - color: #999; - width: 60px; - overflow: hidden; -} -.oe_im_chatview .oe_im_chatview_from { - margin: 0 0 2px 0; - line-height: 14px; - font-weight: bold; - font-size: 12px; - width: 140px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - color: #3A87AD; -} -.oe_im_chatview .oe_im_chatview_bubble_list { -} -.oe_im_chatview .oe_im_chatview_bubble_item { - margin: 0 0 2px 30px; - line-height: 14px; - word-wrap: break-word; -} - -.oe_im_chatview_online { - margin-top: -4px; - width: 11px; - height: 11px; -} - -.oe_im_chatview .oe_im_chatview_date_separator { - background: transparent; - position: relative; - padding: 3px; - color: #aaa; - margin: 3px; - text-align: right; - font-size: 12px; -} \ No newline at end of file diff --git a/addons/im_chat/static/src/js/im_chat_backend.js b/addons/im_chat/static/src/js/im_chat_backend.js deleted file mode 100644 index cd1cc00fdf2..00000000000 --- a/addons/im_chat/static/src/js/im_chat_backend.js +++ /dev/null @@ -1,301 +0,0 @@ -odoo.define('im_chat.im_chat', function (require) { -"use strict"; - -// to do: make this work in website - -var bus = require('bus.bus'); -var core = require('web.core'); -var data = require('web.data'); -var Model = require('web.DataModel'); -var session = require('web.session'); -var SystrayMenu = require('web.SystrayMenu'); -var utils = require('web.utils'); -var web_client = require('web.web_client'); -var Widget = require('web.Widget'); -var im_chat_common = require('im_chat.im_chat_common'); - -var _t = core._t; -var QWeb = core.qweb; - -var USERS_LIMIT = 20; - - -// ########## CONVERSATION extentions ############### - -im_chat_common.ConversationManager.include({ - _message_receive: function(message){ - var self = this; - var session_id = message.to_id[0]; - var conv = this.sessions[message.to_id[1]]; - if(!conv){ - // fetch the session, and init it with the message - var def_session = new Model("im_chat.session").call("session_info", [], {"ids" : [session_id]}).then(function(session){ - conv = self.session_apply(session, {'force_open': true}); - conv.message_receive(message); - }); - }else{ - this._super(message); - } - }, -}); - -im_chat_common.Conversation.include({ - // user actions - prepare_action_menu: function(){ - this._super(); - this._add_action(_t('Shortcuts'), 'im_chat_option_shortcut', 'fa fa-info-circle', this.action_shorcode); - this._add_action(_t('Quit discussion'), 'im_chat_option_quit', 'fa fa-minus-square', this.action_quit_session); - }, - action_shorcode: function(e){ - return web_client.action_manager.do_action({ - type: 'ir.actions.act_window', - name : _t('Shortcode'), - res_model: 'im_chat.shortcode', - view_mode: 'tree,form', - view_type: 'tree', - views: [[false, 'list'], [false, 'form']], - target: "new", - limit: 80, - flags: { - action_buttons: true, - pager: true, - } - }); - }, - action_quit_session: function(e){ - var self = this; - var Session = new Model("im_chat.session"); - return Session.call("quit_user", [this.get("session").uuid]).then(function(res) { - if(! res){ - self.do_warn(_t("Warning"), _t("You are only 2 identified users. Just close the conversation to leave.")); - } - }); - }, - // session - session_update_state: function(state){ - var self = this; - var args = arguments; - var super_call = this._super; - // broadcast the state changing - return new Model("im_chat.session").call("update_state", [], {"uuid" : this.get("session").uuid, "state" : state}).then(function(){ - super_call.apply(self, args); - }); - }, - // window title - window_title_change: function() { - this.super(); - var title = undefined; - if (this.get("waiting_messages") !== 0) { - title = _.str.sprintf(_t("%d Messages"), this.get("waiting_messages")) - } - web_client.set_title_part("im_messages", title); - }, - // TODO : change this way - add_user: function(user){ - return new Model("im_chat.session").call("add_user", [this.get("session").uuid , user.id]); - }, -}); - - - -// ###### BACKEND : contact panel, top menu button ######### - -var UserWidget = Widget.extend({ - template: "im_chat.UserWidget", - events: { - "click": "activate_user", - }, - init: function(parent, user) { - this._super(parent); - this.set("id", user.id); - this.set("name", user.name); - this.set("im_status", user.im_status); - this.set("image_url", user.image_url); - }, - start: function() { - this.$el.data("user", {id:this.get("id"), name:this.get("name")}); - this.$el.draggable({helper: "clone"}); - this.on("change:im_status", this, this.update_status); - this.update_status(); - }, - update_status: function(){ - this.$(".oe_im_user_online").toggle(this.get('im_status') !== 'offline'); - var img_src = (this.get('im_status') == 'away' ? '/im_chat/static/src/img/yellow.png' : '/im_chat/static/src/img/green.png'); - this.$(".oe_im_user_online").attr('src', img_src); - }, - activate_user: function() { - this.trigger("user_clicked", this.get("id")); - }, -}); - -var InstantMessaging = Widget.extend({ - template: "im_chat.InstantMessaging", - events: { - "keydown .oe_im_searchbox": "input_change", - "keyup .oe_im_searchbox": "input_change", - "change .oe_im_searchbox": "input_change", - }, - init: function(parent) { - this._super(parent); - this.shown = false; - this.set("right_offset", 0); - this.set("current_search", ""); - this.users = []; - this.widgets = {}; - - // listen bus - this.bus = bus.bus; - this.bus.on("notification", this, this.on_notification); - }, - start: function() { - var self = this; - // ui - this.conv_manager = new im_chat_common.ConversationManager(this); - this.on("change:right_offset", this.conv_manager, _.bind(function() { - this.conv_manager.set("right_offset", this.get("right_offset")); - }, this)); - $(window).scroll(_.bind(this.position_compute, this)); - $(window).resize(_.bind(this.position_compute, this)); - this.$el.css("right", -this.$el.outerWidth()); - this.position_compute(); - - // business - this.on("change:current_search", this, this.user_search); - - // add a drag & drop listener - self.conv_manager.on("im_session_activated", self, function(conv) { - conv.$el.droppable({ - drop: function(event, ui) { - conv.add_user(ui.draggable.data("user")); - } - }); - }); - - // fetch the unread message and the recent activity (e.i. to re-init in case of refreshing page) - return session.rpc("/im_chat/init", {}).then(function(notifications) { - _.each(notifications, function(notif){ - self.conv_manager.on_notification(notif, {'load_history': true}); - }); - // start polling - self.bus.start_polling(); - }); - }, - on_notification: function(notification){ - var channel = notification[0]; - var message = notification[1]; - // user status notification - if(channel[1] === 'im_chat.presence'){ - if(message.im_status){ - this.user_update_status([message]); - } - } - }, - // ui - position_compute: function() { - var top = 48; - this.$el.css("top", top); - this.$el.css("bottom", 0); - }, - input_change: function() { - this.set("current_search", this.$(".oe_im_searchbox").val()); - }, - switch_display: function() { - this.position_compute(); - var fct = _.bind(function(place) { - this.set("right_offset", place + this.$el.outerWidth()); - this.$(".oe_im_searchbox").focus(); - }, this); - var opt = { - step: fct, - }; - if (this.shown) { - this.$el.animate({ - right: -this.$el.outerWidth(), - }, opt); - } else { - if (! bus.bus.activated) { - this.do_warn("Instant Messaging is not activated on this server. Try later.", ""); - return; - } - // update the list of user status when show the IM - this.user_search(); - this.$el.animate({ - right: 0, - }, opt); - } - this.shown = ! this.shown; - }, - // user methods - user_search: function(e) { - var self = this; - var user_model = new Model("res.users"); - return user_model.call('im_search', [this.get("current_search"), USERS_LIMIT]).then(function(result){ - self.$(".oe_im_input").val(""); - var old_widgets = self.widgets; - self.widgets = {}; - self.users = []; - _.each(result, function(user) { - user.image_url = session.url('/web/image', {model:'res.users', field: 'image_small', id: user.id}); - var widget = new UserWidget(self, user); - widget.appendTo(self.$(".oe_im_users")); - widget.on("user_clicked", self, self.user_clicked); - self.widgets[user.id] = widget; - self.users.push(user); - }); - _.each(old_widgets, function(w) { - w.destroy(); - }); - }); - }, - user_clicked: function(user_id) { - var self = this; - var sessions = new Model("im_chat.session"); - return sessions.call("session_get", [user_id]).then(function(session) { - self.conv_manager.session_apply(session, {'focus': true, 'force_open': true}); - }); - }, - user_update_status: function(users_list){ - var self = this; - _.each(users_list, function(el) { - if (self.widgets[el.id]) { - self.widgets[el.id].set("im_status", el.im_status); - } - }); - } -}); - - -var ImTopButton = Widget.extend({ - template:'im_chat.ImTopButton', - events: { - "click": "clicked", - }, - start: function() { - // Create the InstantMessaging widget and put it in the DOM - var im = new InstantMessaging(this); - web_client.im_messaging = im; - im.appendTo(web_client.$el); - // Bind the click action to the ImTopButton - this.on("clicked", im, im.switch_display); - return this._super(); - }, - clicked: function(ev) { - ev.preventDefault(); - this.trigger("clicked"); - }, -}); - -// Put the ImTopButton widget in the systray menu if the user is an employee -var Users = new Model('res.users'); -Users.call('has_group', ['base.group_user']).done(function(is_employee) { - if (is_employee) { - SystrayMenu.Items.push(ImTopButton); - } -}); - -return { - InstantMessaging: InstantMessaging, - UserWidget: UserWidget, -}; - -}); diff --git a/addons/im_chat/static/src/js/im_chat_common.js b/addons/im_chat/static/src/js/im_chat_common.js deleted file mode 100644 index 0aaa0e68d16..00000000000 --- a/addons/im_chat/static/src/js/im_chat_common.js +++ /dev/null @@ -1,433 +0,0 @@ -odoo.define('im_chat.im_chat_common', function (require) { -"use strict"; - -// to do: make this work in website - -var bus = require('bus.bus'); -var core = require('web.core'); -var session = require('web.session'); -var time = require('web.time'); -var Widget = require('web.Widget'); - -var _t = core._t; -var QWeb = core.qweb; - -var NBR_LIMIT_HISTORY = 20; - - -var ConversationManager = Widget.extend({ - init: function(parent, options) { - var self = this; - this._super(parent); - this.options = _.clone(options) || {}; - _.defaults(this.options, { - inputPlaceholder: _t("Say something..."), - defaultMessage: null, - defaultUsername: _t("Visitor"), - force_open: false, - load_history: true, - focus: false, - }); - // business - this.sessions = {}; - this.bus = bus.bus; - this.bus.on("notification", this, this.on_notification); - this.bus.options.im_presence = true; - - // ui - this.set("right_offset", 0); - this.set("bottom_offset", 0); - this.on("change:right_offset", this, this.calc_positions); - this.on("change:bottom_offset", this, this.calc_positions); - - this.set("window_focus", true); - this.on("change:window_focus", self, function(e) { - self.bus.options.im_presence = self.get("window_focus"); - }); - this.set("waiting_messages", 0); - this.on("change:waiting_messages", this, this.window_title_change); - $(window).on("focus", _.bind(this.window_focus, this)); - $(window).on("blur", _.bind(this.window_blur, this)); - this.window_title_change(); - }, - on_notification: function(notification, options) { - var self = this; - var channel = notification[0]; - var message = notification[1]; - var regex_uuid = new RegExp(/(\w{8}(-\w{4}){3}-\w{12}?)/g); - - // Concern im_chat : if the channel is the im_chat.session or im_chat.status, or a 'private' channel (aka the UUID of a session) - if((Array.isArray(channel) && (channel[1] === 'im_chat.session')) || (regex_uuid.test(channel))){ - // message to display in the chatview - if (message.type === "message" || message.type === "meta") { - self.message_receive(message); - } - // activate the received session - if(message.uuid){ - this.session_apply(message, options); - } - } - }, - - // window focus unfocus beep and title - window_focus: function() { - this.set("window_focus", true); - this.set("waiting_messages", 0); - }, - window_blur: function() { - this.set("window_focus", false); - }, - window_beep: function() { - if (typeof(Audio) === "undefined") { - return; - } - var audio = new Audio(); - var ext = audio.canPlayType("audio/ogg; codecs=vorbis") ? ".ogg" : ".mp3"; - audio.src = session.url("/im_chat/static/src/audio/ting") + ext; - audio.play(); - }, - window_title_change: function() { - var title = undefined; - if (this.get("waiting_messages") !== 0) { - title = _.str.sprintf(_t("%d Messages"), this.get("waiting_messages")); - this.window_beep(); - } - }, - // sessions and messages - session_apply: function(active_session, options){ - // options used by this function : force_open and focus (load_history can be usefull) - var self = this; - options = _.extend(_.clone(this.options), options || {}); - // force open - var session = _.clone(active_session); - if(options['force_open']){ - session.state = 'open'; - } - // create/get the conversation widget - var conv = this.sessions[session.uuid]; - if (! conv) { - if(session.state !== 'closed'){ - conv = new Conversation(this, this, session, options); - conv.appendTo($("body")); - conv.on("destroyed", this, _.bind(this.session_delete, this)); - this.sessions[session.uuid] = conv; - this.calc_positions(); - } - }else{ - conv.set("session", session); - } - // if force open, broadcast it - if(options['force_open'] && active_session.state !== 'open'){ - conv.session_update_state('open'); - } - // apply the focus - if (options['focus']){ - conv.focus(); - } - return conv; - }, - session_delete: function(uuid){ - delete this.sessions[uuid]; - this.calc_positions(); - }, - message_receive: function(message) { - var self = this; - var session_id = message.to_id[0]; - var uuid = message.to_id[1]; - var from_id = message['from_id'] ? message['from_id'][0] : false; - if (! this.get("window_focus") && from_id != this.get_current_uid()) { - this.set("waiting_messages", this.get("waiting_messages") + 1); - } - this._message_receive(message); - }, - _message_receive: function(message){ - var uuid = message.to_id[1]; - var conv = this.sessions[uuid]; - conv.message_receive(message); - }, - // others - get_current_uid: function(){ - return session && !_.isUndefined(session.uid) ? session.uid : false; - }, - calc_positions: function() { - var self = this; - var current = this.get("right_offset"); - _.each(this.sessions, function(s) { - s.set("bottom_position", self.get("bottom_offset")); - s.set("right_position", current); - current += s.$().outerWidth(true); - }); - }, - destroy: function() { - $(window).off("unload", this.unload); - $(window).off("focus", this.window_focus); - $(window).off("blur", this.window_blur); - return this._super(); - } -}); - - -var Conversation = Widget.extend({ - className: "openerp_style oe_im_chatview", - events: { - "keydown input": "keydown", - "click .oe_im_chatview_close": "click_close", - "click .oe_im_chatview_header": "click_header" - }, - init: function(parent, c_manager, session, options) { - this._super(parent); - this.c_manager = c_manager; - this.options = options || {}; - this.loading_history = true; - this.set("messages", []); - this.set("session", session); - this.set("right_position", 0); - this.set("bottom_position", 0); - this.set("pending", 0); - this.inputPlaceholder = this.options.defaultInputPlaceholder; - }, - start: function() { - var self = this; - self._super.apply(this, arguments); - self.prepare_action_menu(); - // ui - self.$().append(QWeb.render("im_chat.Conversation", {widget: self})); - self.on("change:right_position", self, self.calc_pos); - self.on("change:bottom_position", self, self.calc_pos); - self.on("change:pending", self, _.bind(function() { - if (self.get("pending") === 0) { - self.$(".nbr_messages").text(""); - } else { - self.$(".nbr_messages").text("(" + self.get("pending") + ")"); - } - }, self)); - self.full_height = self.$().height(); - self.calc_pos(); - // business - self.bind_action_menu(); - self.on("change:session", self, self.session_update); - self.on("change:messages", self, self.render_messages); - self.$('.oe_im_chatview_content').on('scroll',function(){ - if($(this).scrollTop() === 0){ - self.message_history(); - } - }); - if(self.options['load_history']){ // load history if asked - self.message_history(); - } - // prepare the header and the correct state - self.session_update(); - }, - // action menu - _add_action: function(label, style_class, icon_fa_class, callback){ - this.actions.push({ - 'label': label, - 'class': style_class, - 'icon_class': icon_fa_class, - 'callback': callback, - }); - }, - prepare_action_menu: function(){ - // override this method to add action with _add_action() - this.actions = []; - }, - bind_action_menu: function(){ - var self = this; - _.each(this.actions, function(action){ - if(action.callback){ - self.$('.button_option_group .' + action.class).on('click', _.bind(action.callback, self)); - } - }); - }, - // ui - show: function(){ - this.$().animate({ - height: this.full_height - }); - this.set("pending", 0); - }, - hide: function(){ - this.$().animate({ - height: this.$(".oe_im_chatview_header").outerHeight() - }); - }, - calc_pos: function() { - this.$().css("right", this.get("right_position")); - this.$().css("bottom", this.get("bottom_position")); - }, - // session - session_update_state: function(state){ - var session = this.get('session'); - session.state = state; - this.set('session', session); - }, - session_update: function(){ - // built the name - var names = []; - _.each(this.get("session").users, function(user){ - if( (session.uid !== user.id) && !(_.isUndefined(session.uid) && !user.id) ){ - names.push(user.name); - } - }); - this.$(".header_name").text(names.join(", ")); - this.$(".header_name").attr('title', names.join(", ")); - // update the fold state - if(this.get("session").state){ - if(this.get("session").state === 'closed'){ - this.destroy(); - }else{ - if(this.get("session").state === 'open'){ - this.show(); - }else{ - this.hide(); - } - } - } - }, - // messages - message_history: function(){ - var self = this; - if(this.loading_history){ - var data = {uuid: self.get("session").uuid, limit: NBR_LIMIT_HISTORY}; - var lastid = _.first(this.get("messages")) ? _.first(this.get("messages")).id : false; - if(lastid){ - data.last_id = lastid; - } - session.rpc("/im_chat/history", data).then(function(messages){ - if(messages){ - self.insert_messages(messages); - if(messages.length != NBR_LIMIT_HISTORY){ - self.loading_history = false; - } - }else{ - self.loading_history = false; - } - }); - } - }, - message_receive: function(message) { - if (this.get('session').state === 'open') { - this.set("pending", 0); - } else { - this.set("pending", this.get("pending") + 1); - } - this.insert_messages([message]); - this._go_bottom(); - }, - message_send: function(message, type) { - var self = this; - var send_it = function() { - return session.rpc("/im_chat/post", {uuid: self.get("session").uuid, message_type: type, message_content: message}); - }; - var tries = 0; - send_it().fail(function(error, e) { - e.preventDefault(); - tries += 1; - if (tries < 3) - return send_it(); - }); - }, - insert_messages: function(messages){ - var self = this; - // avoid duplicated messages - messages = _.filter(messages, function(m){ return !_.contains(_.pluck(self.get("messages"), 'id'), m.id) ; }); - // escape the message content and set the timezone - _.map(messages, function(m){ - if(!m.from_id){ - m.from_id = [false,self. get_anonymous_name()]; - } - m.create_date = moment(time.str_to_datetime(m.create_date)).format('YYYY-MM-DD HH:mm:ss'); - return m; - }); - this.set("messages", _.sortBy(this.get("messages").concat(messages), function(m){ return m.id; })); - }, - render_messages: function(){ - var self = this; - var res = {}; - var last_date_day, last_user_id = -1; - _.each(this.get("messages"), function(current){ - // add the url of the avatar for all users in the conversation - current.from_id[2] = session.url(_.str.sprintf("/im_chat/image/%s/%s", self.get('session').uuid, current.from_id[0])); - var date_day = current.create_date.split(" ")[0]; - if(date_day !== last_date_day){ - res[date_day] = []; - last_user_id = -1; - } - last_date_day = date_day; - if(current.type == "message"){ // traditionnal message - if(last_user_id === current.from_id[0]){ - _.last(res[date_day]).push(current); - }else{ - res[date_day].push([current]); - } - last_user_id = current.from_id[0]; - }else{ // meta message - res[date_day].push([current]); - last_user_id = -1; - } - }); - // render and set the content of the chatview - // TODO jem : when refactoring this, don't forget to pre-process date in this function before render quweb template - // since, moment will not be define in qweb on the website pages, because the helper (see csn) is in core.js and cannot be - // imported in the frontend. - this.$('.oe_im_chatview_content_bubbles').html($(QWeb.render("im_chat.Conversation_content", {"list": res}))); - this._go_bottom(); - }, - // utils and event - get_anonymous_name: function(){ - var name = this.options["defaultUsername"]; - _.each(this.get('session').users, function(u){ - if(!u.id){ - name = u.name; - } - }); - return name; - }, - keydown: function(e) { - if(e && e.which == 27) { - if(this.$el.prev().find('.oe_im_chatview_input').length > 0){ - this.$el.prev().find('.oe_im_chatview_input').focus(); - }else{ - this.$el.next().find('.oe_im_chatview_input').focus(); - } - e.stopPropagation(); - this.session_update_state('closed'); - } - if(e && e.which !== 13) { - return; - } - var mes = this.$("input").val(); - if (! mes.trim()) { - return; - } - this.$("input").val(""); - this.message_send(mes, "message"); - }, - _go_bottom: function() { - this.$(".oe_im_chatview_content").scrollTop(this.$(".oe_im_chatview_content").get(0).scrollHeight); - }, - focus: function() { - this.$(".oe_im_chatview_input").focus(); - }, - click_header: function(event){ - var classes = event.target.className.split(' '); - if(_.contains(classes, 'header_name') || _.contains(classes, 'oe_im_chatview_header')){ - this.session_update_state(); - } - }, - click_close: function(event) { - this.session_update_state('closed'); - }, - destroy: function() { - this.trigger("destroyed", this.get('session').uuid); - return this._super(); - } -}); - - -return { - Conversation: Conversation, - ConversationManager: ConversationManager, -}; - -}); \ No newline at end of file diff --git a/addons/im_chat/static/src/xml/im_chat.xml b/addons/im_chat/static/src/xml/im_chat.xml deleted file mode 100644 index eb479863b3e..00000000000 --- a/addons/im_chat/static/src/xml/im_chat.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - -
- - - - -
- - - - -
-
×
-
-
-
-
-
-
- - - - -
- -
- - - - - - - - - - - - - - - -
-
- -
-
-
- -
-
-
-
-
-
- -
-
-
- -
-
-
-
-
-
- - -
- - - - - -
-
- -
-
- - -
-
-
-
-
- -
  • - - - -
  • -
    - diff --git a/addons/im_chat/views/im_chat_session_templates.xml b/addons/im_chat/views/im_chat_session_templates.xml deleted file mode 100644 index 7007e148699..00000000000 --- a/addons/im_chat/views/im_chat_session_templates.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/addons/im_livechat/__openerp__.py b/addons/im_livechat/__openerp__.py index 5d460e6f58e..da672662eca 100644 --- a/addons/im_livechat/__openerp__.py +++ b/addons/im_livechat/__openerp__.py @@ -21,16 +21,20 @@ Help your customers with this chat, and analyse their feedback. 'data': [ "security/im_livechat_channel_security.xml", "security/ir.model.access.csv", - "views/im_chat_session_views.xml", + "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", "im_chat"], + '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..152e1374fb5 100644 --- a/addons/im_livechat/controllers/main.py +++ b/addons/im_livechat/controllers/main.py @@ -1,52 +1,23 @@ # -*- coding: utf-8 -*- -import json - from openerp import http, _ -from openerp import SUPERUSER_ID from openerp.http import request class LivechatController(http.Controller): - @http.route('/im_livechat/support//', 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["dbname"] = dbname - info["channel"] = channel_id - info["channel_name"] = Channel.browse(channel_id).name - return request.render('im_livechat.support_page', info) + @http.route('/im_livechat/support/', type='http', auth='public') + def support_page(self, channel_id, **kwargs): + channel = request.env['im_livechat.channel'].sudo().browse(channel_id) + return request.render('im_livechat.support_page', {'channel': channel}) - @http.route('/im_livechat/loader//', type='http', auth='none') - def loader(self, dbname, channel_id, **kwargs): - info = request.env['im_livechat.channel'].sudo().get_channel_infos(channel_id) - info["dbname"] = dbname - info["channel"] = channel_id - info["username"] = kwargs.get("username", _("Visitor")) - # find the country from the request - country_id = False - country_code = request.session.geoip and request.session.geoip.get('country_code') or False - if country_code: - countries = request.env['res.country'].search([('code', '=', country_code)]) - if countries: - country_id = countries[0] - # extract url - url = request.httprequest.headers.get('Referer') or request.httprequest.base_url - # find the match rule for the given country and url - rule = request.env['im_livechat.channel.rule'].match_rule(channel_id, url, country_id) - if rule: - if rule.action == 'hide_button': - # 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, - } - info['rule'] = json.dumps(rule and rule_data or False) - return request.render('im_livechat.loader', info) + @http.route('/im_livechat/loader/', type='http', auth='public') + def loader(self, channel_id, **kwargs): + username = kwargs.get("username", _("Visitor")) + channel = request.env['im_livechat.channel'].sudo().browse(channel_id) + info = request.env['im_livechat.channel'].match_rules(request, channel.id, username=username) + return request.render('im_livechat.loader', {'info': info}) if info else False - @http.route('/im_livechat/get_session', type="json", auth="none") + @http.route('/im_livechat/get_session', type="json", auth='public') def get_session(self, channel_id, anonymous_name, **kwargs): # if geoip, add the country name to the anonymous name if request.session.geoip: @@ -54,43 +25,34 @@ 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"].get_mail_channel(channel_id, anonymous_name) - @http.route('/im_livechat/available', type='json', auth="none") - def available(self, db, channel): - user_id = request.session.uid or SUPERUSER_ID - 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='public') + 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) + 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 @@ days -1 - im_chat.session + mail.channel cron_remove_empty_session @@ -28,47 +28,20 @@ - - - Sessions by day - im_livechat.report - [('nbr_speakers','>', 1)] - {'group_by': ['start_date:day', 'session_name']} - v - - - - - Last 24h stats - im_livechat.report - [('start_date','>', (context_today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d') )] - {'group_by': ['start_date:day', 'session_name']} - - - - - Operators stats - im_livechat.report - [('nbr_speakers','>', 1), ('user_id', '!=', False)] - {'group_by': ['start_date:month', 'user_id', 'uuid']} - - - - - + :rating_0 <img src='/rating/static/src/img/rating_0.png'/> Rating Smiley Bad - + :rating_5 <img src='/rating/static/src/img/rating_5.png'/> Rating Smiley Okay - + :rating_10 <img src='/rating/static/src/img/rating_10.png'/> Rating Smiley Good diff --git a/addons/im_livechat/models/__init__.py b/addons/im_livechat/models/__init__.py index 4caf13411c9..687ed1047e5 100644 --- a/addons/im_livechat/models/__init__.py +++ b/addons/im_livechat/models/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -* import im_livechat_channel -import im_chat_session +import mail_channel import rating diff --git a/addons/im_livechat/models/im_chat_session.py b/addons/im_livechat/models/im_chat_session.py deleted file mode 100644 index a5af87d5580..00000000000 --- a/addons/im_livechat/models/im_chat_session.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -from openerp import api, fields, models -from openerp import SUPERUSER_ID - - -class ImChatSession(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'] - - - 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) - - @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. - """ - self.ensure_one() - if self.anonymous_name and self._uid == SUPERUSER_ID: - return True - else: - return super(ImChatSession, self).is_in_session() - - @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 - :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 - - @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) - - @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() diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py index 85b202e002f..3514e8d6d93 100644 --- a/addons/im_livechat/models/im_livechat_channel.py +++ b/addons/im_livechat/models/im_livechat_channel.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import json import openerp import random import re @@ -24,13 +25,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 @@ -38,9 +38,8 @@ class ImLivechatChannel(models.Model): help="URL to a static page where you client can discuss with the operator of the channel.") are_you_inside = fields.Boolean(string='Are you inside the matrix?', compute='_are_you_inside', store=False, readonly=True) - script_internal = fields.Text('Script (internal)', compute='_compute_script_internal', 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 @@ -57,7 +56,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') @@ -75,17 +74,6 @@ class ImLivechatChannel(models.Model): self.image_small = False self.image_medium = False - @api.multi - def _compute_script_internal(self): - view = self.env['ir.model.data'].get_object('im_livechat', 'internal_loader') - values = { - "url": self.env['ir.config_parameter'].sudo().get_param('web.base.url'), - "dbname": self._cr.dbname, - } - for record in self: - values["channel"] = record.id - record.script_internal = view.render(values) - @api.multi def _compute_script_external(self): view = self.env['ir.model.data'].get_object('im_livechat', 'external_loader') @@ -94,26 +82,26 @@ class ImLivechatChannel(models.Model): "dbname": self._cr.dbname, } for record in self: - values["channel"] = record.id + values["channel_id"] = record.id record.script_external = view.render(values) @api.multi def _compute_web_page_link(self): base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') for record in self: - record.web_page = "%s/im_livechat/support/%s/%i" % (base_url, self._cr.dbname, record.id) + record.web_page = "%s/im_livechat/support/%i" % (base_url, 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'] @@ -121,7 +109,6 @@ class ImLivechatChannel(models.Model): else: record.rating_percentage_satisfaction = -1 - # -------------------------- # Action Methods # -------------------------- @@ -143,7 +130,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 # -------------------------- @@ -158,33 +145,77 @@ 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): - url = self.env['ir.config_parameter'].get_param('web.base.url') channel = self.browse(channel_id) return { - "url": url, - 'buttonText': channel.button_text, - 'inputPlaceholder': channel.input_placeholder, - 'defaultMessage': channel.default_message, - "channelName": channel.name, + 'button_text': channel.button_text, + 'input_placeholder': channel.input_placeholder, + 'default_message': channel.default_message, + "channel_name": channel.name, + "channel_id": channel.id, } + @api.model + def match_rules(self, request, channel_id, username='Visitor'): + info = { + 'server_url': self.env['ir.config_parameter'].get_param('web.base.url'), + 'options': self.sudo().get_channel_infos(channel_id), + } + info['options']["default_username"] = username + # find the country from the request + country_id = False + country_code = request.session.geoip and request.session.geoip.get('country_code') or False + if country_code: + country_ids = self.env['res.country'].sudo().search([('code', '=', country_code)]) + if country_ids: + country_id = country_ids[0] + # extract url + url = request.httprequest.headers.get('Referer') or request.httprequest.base_url + # find the match rule for the given country and url + rule = self.env['im_livechat.channel.rule'].sudo().match_rule(channel_id, url, country_id) + if rule: + if rule.action == 'hide_button': + # don't return the initialization script, since its blocked (in the country) + return False + rule_data = { + 'action': rule.action, + 'auto_popup_timer': rule.auto_popup_timer, + 'regex_url': rule.regex_url, + } + info['rule'] = rule and rule_data or False + return info class ImLivechatChannelRule(models.Model): @@ -214,7 +245,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 new file mode 100644 index 00000000000..a55e33db3d3 --- /dev/null +++ b/addons/im_livechat/models/mail_channel.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +from openerp import api, fields, models +from openerp import SUPERUSER_ID + + +class MailChannel(models.Model): + """ Chat Session + Reprensenting a conversation between users. + It extends the base method for anonymous usage. + """ + + _name = 'mail.channel' + _inherit = ['mail.channel', 'rating.mixin'] + + anonymous_name = fields.Char('Anonymous Name') + create_date = fields.Datetime('Create Date', required=True) + channel_type = fields.Selection(selection_add=[('livechat', 'Livechat Conversation')]) + livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel') + + @api.multi + 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. + """ + 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 channel_info(self): + """ Extends the channel header by adding the livechat operator and the 'anonymous' profile + :rtype : list(dict) + """ + 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 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): + 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.py b/addons/im_livechat/report/im_livechat_report.py deleted file mode 100644 index 4fa0436b001..00000000000 --- a/addons/im_livechat/report/im_livechat_report.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -from openerp import api, fields, models, tools - - -class im_livechat_report(models.Model): - """ Livechat Support Report """ - - _name = "im_livechat.report" - _description = "Livechat Support Report" - _order = 'start_date, session_name' - _auto = False - - session_name = fields.Char('Session Name', readonly=True) - uuid = fields.Char('UUID', 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) - - - 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') - cr.execute(""" - CREATE OR REPLACE VIEW im_livechat_report 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 - ) - """) diff --git a/addons/im_livechat/report/im_livechat_report_channel.py b/addons/im_livechat/report/im_livechat_report_channel.py new file mode 100644 index 00000000000..5e5c012753e --- /dev/null +++ b/addons/im_livechat/report/im_livechat_report_channel.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +from openerp import fields, models, tools + + +class ImLivechatReportChannel(models.Model): + """ Livechat Support Report on the Channels """ + + _name = "im_livechat.report.channel" + _description = "Livechat Support Report" + _order = 'start_date, technical_name' + _auto = False + + 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)") + 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_channel') + cr.execute(""" + CREATE OR REPLACE VIEW im_livechat_report_channel AS ( + SELECT + 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_views.xml b/addons/im_livechat/report/im_livechat_report_channel_views.xml similarity index 51% rename from addons/im_livechat/report/im_livechat_report_views.xml rename to addons/im_livechat/report/im_livechat_report_channel_views.xml index 65ad87120dd..d8d9ed7dcfa 100644 --- a/addons/im_livechat/report/im_livechat_report_views.xml +++ b/addons/im_livechat/report/im_livechat_report_channel_views.xml @@ -2,42 +2,42 @@ - - im_livechat.report.pivot - im_livechat.report + + im_livechat.report.channel.pivot + im_livechat.report.channel - + - - + - - im_livechat.report.graph - im_livechat.report + + im_livechat.report.channel.graph + im_livechat.report.channel - - + + - - im_livechat.report.search - im_livechat.report + + im_livechat.report.channel.search + im_livechat.report.channel - - + + + - + @@ -49,22 +49,29 @@ - - Livechat Support Report - im_livechat.report + + Livechat Support Report Channel + im_livechat.report.channel form pivot,graph - 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. + {"search_default_last_week":1} + 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. - + groups="im_livechat_group_manager"/> + + + 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 @@ + + + + + + im_livechat.report.operator.pivot + im_livechat.report.operator + + + + + + + + + + + im_livechat.report.operator.graph + im_livechat.report.operator + + + + + + + + + + im_livechat.report.operator.search + im_livechat.report.operator + + + + + + + + + + + + + + + + + + + + Livechat Support Report Channel + im_livechat.report.operator + form + pivot,graph + {"search_default_last_week":1} + 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. + + + + + + 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 @@ - + Live Support Managers can read messages from live support - + - [('to_id.channel_id', '!=', None)] + [('mail.channel_ids.livechat_channel_id', '!=', None)] - + Live Support Managers can see all session from live support - + - [('channel_id', '!=', None)] + [('mail.channel_ids.livechat_channel_id', '!=', None)] diff --git a/addons/im_livechat/security/ir.model.access.csv b/addons/im_livechat/security/ir.model.access.csv index f6cc4ed628d..c8c52517b7a 100644 --- a/addons/im_livechat/security/ir.model.access.csv +++ b/addons/im_livechat/security/ir.model.access.csv @@ -1,8 +1,9 @@ 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_channel_user,im_livechat.channel.user,model_im_livechat_channel,im_livechat_group_user,1,1,1,0 +access_livechat_channel_manager,im_livechat.channel.manager,model_im_livechat_channel,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 +access_livechat_channel_rule_manager,im_livechat.channel.rule,model_im_livechat_channel_rule,im_livechat_group_manager,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..dc947b66285 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; +} + + +.o_livechat_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 c0f3bde83e5..49a02a6f155 100644 --- a/addons/im_livechat/static/src/js/im_livechat.js +++ b/addons/im_livechat/static/src/js/im_livechat.js @@ -7,23 +7,24 @@ 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('im_chat.im_chat_common'); +var mail_chat_common = require('mail.chat_common'); var _t = core._t; var QWeb = core.qweb; -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. + * Conversation Patch + * + * 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(); @@ -49,30 +50,38 @@ im_chat_common.Conversation.include({ this.show(); } } + // session and cookie update + utils.set_cookie('im_livechat_session', JSON.stringify(this.get('session')), 60*60); } - // session and cookie update this._super(state); - utils.set_cookie(COOKIE_NAME, JSON.stringify(this.get('session')), 60*60); }, - click_close: function(event) { + on_click_close: function(event) { + var self = this; + event.stopPropagation(); 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); + var args = arguments; + var _super = this._super; + this.feedback.on("feedback_sent", this, function(){ + _super.apply(self, args); + }); }else{ this._super.apply(this, arguments); - utils.set_cookie(COOKIE_NAME, "", -1); - utils.set_cookie('im_livechat_auto_popup', JSON.stringify(false), 60*60); } }, }); -// To avoid exeption when the anonymous has close his -// conversation and when operator send him a message. -im_chat_common.ConversationManager.include({ +/* + * Livechat Conversation Manager + * + * To avoid exeption when the anonymous has close his conversation and when operator send + * him a message. Extending ConversationManager is better than monkey-patching it. + */ +var LivechatConversationManager = mail_chat_common.ConversationManager.extend({ message_receive: function(message) { try{ this._super(message); @@ -80,145 +89,143 @@ im_chat_common.ConversationManager.include({ } }); - -var LiveSupport = Widget.extend({ - init: function(server_url, db, channel, options, rule) { - this._super(); - options = options || {}; - _.defaults(options, { - buttonText: _t("Chat with one of our collaborators"), - inputPlaceholder: null, - defaultMessage: _t("How may I help you?"), - defaultUsername: _t("Visitor"), +/** + * Livechat Button + * + * This widget is the Button to start a conversation with an operator on the livechat channel + * (defined in options.channel_id). This will auto-popup if the rule say so, create the conversation + * window if a session is saved in a cookie, ... + */ +var LivechatButton = Widget.extend({ + template: 'im_livechat.LivechatButton', + events: { + "click": "on_click" + }, + init: function(parent, server_url, options, rule) { + this._super.apply(this, arguments); + this.server_url = server_url; + this.options = _.defaults(options || {}, { + placeholder: _t('Ask something ...'), + default_username: _t("Visitor"), + button_text: _t("Chat with one of our collaborators"), + default_message: _t("How may I help you?"), }); - user_session.setup(server_url, {use_cors: false}); - this.load_template(db, channel, options, rule); + this.rule = rule || false; + this.mail_channel = false; }, - _get_template_list: function(){ - return ['/im_livechat/static/src/xml/im_livechat.xml', '/im_chat/static/src/xml/im_chat.xml']; + willStart: function(){ + return $.when(this.load_qweb_template(), this._super()); }, - load_template: function(db, channel, options, rule){ + start: function(){ + var self = this; + return this._super.apply(this, arguments).then(function(){ + // set up the manager + self.manager = new LivechatConversationManager(self, self.options); + self.manager.set("bottom_offset", self.$el.outerHeight()); + // check if a session already exists in a cookie + var cookie = utils.get_cookie('im_livechat_session'); + if(cookie){ + self.set_conversation(JSON.parse(cookie), false); + }else{ // if not session, apply the rule + var auto_popup_cookie = utils.get_cookie('im_livechat_auto_popup') ? JSON.parse(utils.get_cookie('im_livechat_auto_popup')) : true; + self.auto_popup_cookie = auto_popup_cookie; + if (self.rule.action === 'auto_popup' && auto_popup_cookie){ + setTimeout(function() { + self.on_click(); + }, self.rule.auto_popup_timer*1000); + } + } + }); + }, + load_qweb_template: function(){ var self = this; - // load the qweb templates var defs = []; - var templates = this._get_template_list(); + var templates = ['/mail/static/src/xml/mail_chat_common.xml', '/im_livechat/static/src/xml/im_livechat.xml']; _.each(templates, function(tmpl){ defs.push(user_session.rpc('/web/proxy/load', {path: tmpl}).then(function(xml) { QWeb.add_template(xml); })); }); - return $.when.apply($, defs).then(function() { - self.setup(db, channel, options, rule); + return $.when.apply($, defs); + }, + load_mail_channel: function(display_warning){ + var self = this; + return user_session.rpc('/im_livechat/get_session', { + "channel_id" : this.options.channel_id, + "anonymous_name" : this.options.default_username, + }, {shadow: true}).then(function(channel){ + if(channel){ + self.set_conversation(channel, true); + }else{ + if(display_warning){ + self.alert_available_message(); + } + } }); }, - setup: function(db, channel, options, rule){ + on_click: function(event){ var self = this; - var cookie = utils.get_cookie(COOKIE_NAME); - if(cookie){ - self.build_button(channel, options, JSON.parse(cookie), rule); - }else{ - user_session.rpc("/im_livechat/available", {db: db, channel: channel}).then(function(activated) { - if(activated){ - self.build_button(channel, options, false, rule); - } - }); + if(!this.mail_channel){ + this.load_mail_channel(event !== undefined); } }, - build_button: function(channel, options, session, rule){ - var button = new ChatButton(null, channel, options, session); - button.appendTo($("body")); - var auto_popup_cookie = utils.get_cookie('im_livechat_auto_popup') ? JSON.parse(utils.get_cookie('im_livechat_auto_popup')) : true; - if (rule.action === 'auto_popup' && auto_popup_cookie){ - setTimeout(function() { - button.click(); - }, rule.auto_popup_timer*1000); - } - } -}); - -var ChatButton = Widget.extend({ - className: "openerp_style oe_chat_button hidden-print", - events: { - "click": "click" - }, - init: function(parent, channel, options, session) { - this._super(parent); - this.channel = channel; - this.options = options; - this.text = options.buttonText; - this.session = session || false; - this.conv = false; - this.no_session_message = _t("None of our collaborators seems to be available, please try again later."); - }, - 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.set("bottom_offset", $('.oe_chat_button').outerHeight()); - if(this.session){ - this.set_conversation(this.session); - } - }, - click: function() { + set_conversation: function(mail_channel, welcome_message){ var self = this; - if (!this.conv){ - user_session.rpc("/im_livechat/get_session", { - "channel_id" : self.channel, - "anonymous_name" : this.options.defaultUsername - }, {shadow: true}).then(function(session) { - if (! session) { - alert(self.no_session_message); - return; - } - session.state = 'open'; - // save the session in a cookie - utils.set_cookie(COOKIE_NAME, JSON.stringify(session), 60*60); - // create the conversation with the received session - self.set_conversation(session, true); - }); - } - }, - set_conversation: function(session, welcome_message){ - var self = this; - this.session = session; - if(session.state === 'closed'){ - return; - } - this.conv = this.manager.session_apply(session); + // set the mail_channel and create the Conversation Window + this.mail_channel = mail_channel; + this.conv = this.manager.session_apply(mail_channel, { + 'load_history': !welcome_message, + 'focus': welcome_message + }); this.conv.on("destroyed", this, function() { bus.bus.stop_polling(); delete self.conv; - delete self.session; + delete self.mail_channel; + utils.set_cookie('im_livechat_session', "", -1); // delete the session cookie }); - // start the polling - bus.bus.add_channel(session.uuid); - bus.bus.start_polling(); - // add the automatic welcome message - if(welcome_message){ - this.send_welcome_message(); - } + // setup complet when the conversation window is appended + this.manager._session_defs[mail_channel.id].then(function(){ + // start the polling + bus.bus.add_channel(mail_channel.uuid); + bus.bus.start_polling(); + // add the automatic welcome message + if(welcome_message){ + self.send_welcome_message(); + } + // create the cookies + utils.set_cookie('im_livechat_auto_popup', JSON.stringify(false), 60*60); + utils.set_cookie('im_livechat_session', JSON.stringify(mail_channel), 60*60); + }) }, 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.mail_channel.operator_pid && this.options.default_message) { + setTimeout(function(){ + self.conv.message_receive({ + id : 1, + message_type: "comment", + model: 'mail.channel', + body: self.options.default_message, + date: time.datetime_to_str(new Date()), + author_id: self.mail_channel.operator_pid, + channel_ids: [self.mail_channel.id], + tracking_value_ids: [], + attachment_ids: [], + }); + }, 1000); } - } -}); + }, + alert_available_message: function(){ + alert(_t("None of our collaborators seems to be available, please try again later.")); + }, +}) -/* Rating livechat object */ +/* + * Rating for Livechat + * + * This widget display the 3 rating smileys, and a textarea to add a reason (only for red + * smileys), and sent the user feedback to the server. + */ var Feedback = Widget.extend({ template : "im_livechat.feedback", init: function(parent){ @@ -231,20 +238,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 +267,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"); @@ -271,7 +278,7 @@ var Feedback = Widget.extend({ return { Feedback: Feedback, - LiveSupport: LiveSupport, + LivechatButton: LivechatButton, }; }); 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..bc970d37e06 --- /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'); + }); + self.$el.on('click', '.o_mail_chat_channel_unpin', _.bind(self.on_click_channel_unpin, self)); + return this._super.apply(this, arguments).then(function(){ + }); + }, + 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..614b358c852 100644 --- a/addons/im_livechat/static/src/xml/im_livechat.xml +++ b/addons/im_livechat/static/src/xml/im_livechat.xml @@ -2,28 +2,29 @@ - - + +
    + +
    -
    -
    +
    +
    Did we correctly answer your question ?
    -
    +
    Good OK Bad
    -
    +
    -
    +
    - - \ No newline at end of file + 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..456e293a093 --- /dev/null +++ b/addons/im_livechat/static/src/xml/im_livechat_backend.xml @@ -0,0 +1,22 @@ + + diff --git a/addons/im_livechat/views/im_livechat_channel_templates.xml b/addons/im_livechat/views/im_livechat_channel_templates.xml index 767d3986e84..3079f6f97f1 100644 --- a/addons/im_livechat/views/im_livechat_channel_templates.xml +++ b/addons/im_livechat/views/im_livechat_channel_templates.xml @@ -2,8 +2,20 @@ - -