[MERGE] New view Messaging
@@ -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)
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import bus
|
||||
import bus_presence
|
||||
import res_users
|
||||
import res_partner
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,114 +0,0 @@
|
||||
<templates>
|
||||
<t t-name="gamification.UserWallSidebar" class="oe_gamification_user_wall_sidebar">
|
||||
<div>
|
||||
<div class="oe_gamification_suggestion"></div>
|
||||
<div class="oe_gamification_challenge_list"></div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="gamification.ChallengeSummary">
|
||||
<div class="oe_goal">
|
||||
<div class="oe_gamification_title">
|
||||
<i class="oe_update_challenge fa fa-refresh" rol="button" t-attf-id="{challenge.id}"/>
|
||||
<span><t t-esc="challenge.name" /></span>
|
||||
</div>
|
||||
|
||||
<t t-if="challenge.visibility_mode == 'personal'">
|
||||
<div class="oe_table oe_goals_list">
|
||||
<div t-foreach="challenge.lines" t-as="line" t-attf-class="oe_row oe_goal_outer_box #{line.state == 'reached' ? 'oe_goal_reached' : ''} #{line.display_mode != 'progress' ? 'oe_no_progress' : ''}">
|
||||
<t t-if="line.display_mode == 'progress'">
|
||||
<div class="oe_goal_progress_background"></div>
|
||||
<div class="oe_goal_progress" t-attf-style="#{line.display_mode == 'progress' ? 'width: '+line.completeness+'%;' : 'width:0;'}"></div>
|
||||
<div class="oe_cell oe_goal_current"><t t-esc="line.current" /></div>
|
||||
</t>
|
||||
<div class="oe_cell">
|
||||
<t t-if="line.computation_mode != 'manually' and !line.action">
|
||||
<span t-att-title="line.description"><t t-esc="line.name" /></span>
|
||||
</t>
|
||||
<t t-if="line.action or line.computation_mode == 'manually'">
|
||||
<span t-att-title="line.description"><a class="oe_goal_action" t-att-id="line.id"><t t-esc="line.name" /></a></span>
|
||||
</t>
|
||||
<t t-if="line.display_mode == 'progress'"><br/>
|
||||
<div class="oe_grey">
|
||||
<t t-if="line.definition_condition == 'higher'">
|
||||
Target:
|
||||
</t>
|
||||
<t t-if="line.definition_condition == 'lower'">
|
||||
Target: <=
|
||||
</t>
|
||||
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="line.target"/></span>
|
||||
<t t-if="line.suffix"><t t-esc="line.suffix"/></t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="challenge.visibility_mode == 'ranking'">
|
||||
<div t-foreach="challenge.lines" t-as="line" class="oe_goals_list oe_table">
|
||||
<div class="oe_row">
|
||||
<div class="oe_cell oe_thead" colspan="3" t-attf-title="#{line.description ? line.description : ''}">
|
||||
<strong><t t-esc="line.name"/></strong>
|
||||
<br/>
|
||||
<div class="oe_grey">
|
||||
<t t-if="line.definition_condition == 'higher'">
|
||||
Target:
|
||||
</t>
|
||||
<t t-if="line.definition_condition == 'lower'">
|
||||
Target: <=
|
||||
</t>
|
||||
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="line.target" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div t-foreach="line.goals" t-as="goal" t-attf-class="#{goal.id == line.own_goal_id ? 'oe_bold' : ''}">
|
||||
<div t-attf-class="oe_row oe_goal_outer_box #{goal.state == 'reached' ? 'oe_goal_reached' : ''} #{line.display_mode != 'progress' ? 'oe_no_progress' : ''}">
|
||||
<t t-if="line.display_mode == 'progress'">
|
||||
<div class="oe_goal_progress_background"></div>
|
||||
<div class="oe_goal_progress" t-attf-style="#{line.display_mode == 'progress' ? 'width: '+goal.completeness+'%;' : 'width:0;'}"></div>
|
||||
</t>
|
||||
|
||||
<div class="oe_cell col0"><t t-esc="goal.rank" /></div>
|
||||
<div class="oe_cell col1"><img class="oe_user_avatar" t-attf-alt="#{goal.name}" t-attf-data-id="#{goal.user_id}"/></div>
|
||||
<div class="oe_cell col2">
|
||||
<t t-if="line.display_mode == 'progress'">
|
||||
<!-- progress, action on current value -->
|
||||
<t t-esc="goal.name"/><br/>
|
||||
<t t-if="goal.id != line.own_goal_id or (line.computation_mode != 'manually' and !line.action)">
|
||||
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="goal.current" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
|
||||
</t>
|
||||
<t t-if="goal.id == line.own_goal_id and (line.action or line.computation_mode == 'manually')">
|
||||
<a class="oe_goal_action" t-att-id="line.own_goal_id">
|
||||
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="goal.current" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
|
||||
</a>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="line.display_mode != 'progress'">
|
||||
<!-- not progress, action on user name -->
|
||||
<t t-if="goal.id != line.own_goal_id or (line.computation_mode != 'manually' and !line.action)">
|
||||
<t t-esc="goal.name"/>
|
||||
</t>
|
||||
<t t-if="goal.id == line.own_goal_id and (line.action or line.computation_mode == 'manually')">
|
||||
<a class="oe_goal_action" t-att-id="line.own_goal_id"><t t-esc="goal.name"/></a>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="gamification.ChallengeSuggestion">
|
||||
<div class="oe_goal">
|
||||
<h4>Invited Challenges</h4>
|
||||
<ul t-foreach="challenges" t-as="challenge" class="oe_goals_list">
|
||||
<li>
|
||||
<strong><a class="oe_challenge_reply" t-attf-title="#{challenge.description.value ? challenge.description.value : ''}" t-attf-id="{challenge.id}" title="more details..."><t t-esc="challenge.name"/></a></strong>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -4,7 +4,6 @@
|
||||
<template id="assets_backend" name="gamification assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/gamification/static/src/css/gamification.css"/>
|
||||
<script type="text/javascript" src="/gamification/static/src/js/gamification.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="ir.actions.act_window" id="mail.mail_message_action_inbox">
|
||||
<field name="name">Inbox</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">timeline</field>
|
||||
<field name="context">{
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
'options' : {'view_mailbox': True,
|
||||
'view_inbox': True,
|
||||
'show_write_to_followers': True}
|
||||
}</field>
|
||||
<field name="domain">[
|
||||
('needaction', '=', True)
|
||||
]</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import models
|
||||
import controllers
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*
|
||||
import main
|
||||
@@ -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/<string:uuid>/<int:user_id>', '/im_chat/image/<string:uuid>/<string:user_id>'], 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()
|
||||
@@ -1,158 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Smileys -->
|
||||
<record id="im_chat_smiley_smile" model="im_chat.shortcode">
|
||||
<field name="source">:)</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/smile.png'/></field>
|
||||
<field name="description">Emoji Smile</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_sad" model="im_chat.shortcode">
|
||||
<field name="source">:(</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/sad.png'/></field>
|
||||
<field name="description">Emoji Sad</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_laugh" model="im_chat.shortcode">
|
||||
<field name="source">:D</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/laugh.png'/></field>
|
||||
<field name="description">Emoji Laugh</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_wink" model="im_chat.shortcode">
|
||||
<field name="source">;)</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/wink.png'/></field>
|
||||
<field name="description">Emoji Wink</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_stuck" model="im_chat.shortcode">
|
||||
<field name="source">O_0</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/stuck_out_tongue.png'/></field>
|
||||
<field name="description">Emoji Stuck Out Tongue Winking Eye</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_scream" model="im_chat.shortcode">
|
||||
<field name="source">:@</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/scream.png'/></field>
|
||||
<field name="description">Emoji Scream</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_neutral" model="im_chat.shortcode">
|
||||
<field name="source">:|</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/neutral.png'/></field>
|
||||
<field name="description">Emoji Speechless/Neutral face</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_cheeky" model="im_chat.shortcode">
|
||||
<field name="source">:p</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/yum.png'/></field>
|
||||
<field name="description">Emoji Cheeky</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_confused" model="im_chat.shortcode">
|
||||
<field name="source">:/</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/confused.png'/></field>
|
||||
<field name="description">Emoji Confused</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_open_mouth" model="im_chat.shortcode">
|
||||
<field name="source">:O</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/open_mouth.png'/></field>
|
||||
<field name="description">Emoji Open Mouth</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_heart" model="im_chat.shortcode">
|
||||
<field name="source">&lt;3</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/heart.png'/></field>
|
||||
<field name="description">Emoji Heart</field>
|
||||
</record>
|
||||
|
||||
<!-- Other images -->
|
||||
<record id="im_chat_smiley_thumbsup" model="im_chat.shortcode">
|
||||
<field name="source">:+1</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/thumbsup.png'/></field>
|
||||
<field name="description">Emoji Thumbs Up</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_thumbsdown" model="im_chat.shortcode">
|
||||
<field name="source">:-1</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/thumbsdown.png'/></field>
|
||||
<field name="description">Emoji Thumbs Down</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_kitten" model="im_chat.shortcode">
|
||||
<field name="source">:kitten</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/cat.png'/></field>
|
||||
<field name="description">Emoji Kitten</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_pinky" model="im_chat.shortcode">
|
||||
<field name="source">:pinky</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/pinky.png'/></field>
|
||||
<field name="description">Emoji Pinky</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_musti" model="im_chat.shortcode">
|
||||
<field name="source">:musti</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/musti.png'/></field>
|
||||
<field name="description">Emoji Musti</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_man_with_turban" model="im_chat.shortcode">
|
||||
<field name="source">:turban</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/man_with_turban.png'/></field>
|
||||
<field name="description">Emoji Man With Turban</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_troll" model="im_chat.shortcode">
|
||||
<field name="source">:troll</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/trollface.png'/></field>
|
||||
<field name="description">Emoji Troll Face</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_boar" model="im_chat.shortcode">
|
||||
<field name="source">:boar</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/boar.png'/></field>
|
||||
<field name="description">Emoji Boar</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_poop" model="im_chat.shortcode">
|
||||
<field name="source">:poop</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/poop.png'/></field>
|
||||
<field name="description">Emoji Poop</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_cookie" model="im_chat.shortcode">
|
||||
<field name="source">:cookie</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/cookie.png'/></field>
|
||||
<field name="description">Emoji Cookie</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_postal_horn" model="im_chat.shortcode">
|
||||
<field name="source">:postal_horn</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/postal_horn.png'/></field>
|
||||
<field name="description">Emoji Postal Horn</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_ghost" model="im_chat.shortcode">
|
||||
<field name="source">:ghost</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/ghost.png'/></field>
|
||||
<field name="description">Emoji Ghost</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_beer" model="im_chat.shortcode">
|
||||
<field name="source">:beer</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/beer.png'/></field>
|
||||
<field name="description">Emoji Beer</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_smiley_man" model="im_chat.shortcode">
|
||||
<field name="source">:man</field>
|
||||
<field name="substitution"><img src='/im_chat/static/src/img/man.png'/></field>
|
||||
<field name="description">Emoji Man</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <dimitrovden@gmail.com>\n"
|
||||
"Language-Team: Bulgarian <bg@li.org>\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 "Неизвестен"
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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) <thorsten.vocks@big-"
|
||||
"consulting.net>\n"
|
||||
"Language-Team: German <de@li.org>\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"
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <pedro.baeza@gmail.com>\n"
|
||||
"Language-Team: Spanish <es@li.org>\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"
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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ć <bole@dajmi5.com>\n"
|
||||
"Language-Team: Croatian <hr@li.org>\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 ""
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2014-09-23 16:27+0000\n"
|
||||
"PO-Revision-Date: 2014-10-28 07:51+0000\n"
|
||||
"Last-Translator: krnkris <Unknown>\n"
|
||||
"Language-Team: Hungarian <hu@li.org>\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"
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <tome.barbov@eskon.com.mk>\n"
|
||||
"Language-Team: Macedonian <mk@li.org>\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 "непознато"
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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) <Unknown>\n"
|
||||
"Language-Team: Dutch <nl@li.org>\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"
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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) "
|
||||
"<monika.grzelak@openglobe.pl>\n"
|
||||
"Language-Team: Polish <pl@li.org>\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"
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <Unknown>\n"
|
||||
"Language-Team: Portuguese <pt@li.org>\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"
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 "
|
||||
"<webmaster@zupy.com.br>\n"
|
||||
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\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"
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2014-09-23 16:27+0000\n"
|
||||
"PO-Revision-Date: 2014-10-16 04:31+0000\n"
|
||||
"Last-Translator: Dorin <dhongu@gmail.com>\n"
|
||||
"Language-Team: Romanian <ro@li.org>\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"
|
||||
@@ -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 <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <Unknown>\n"
|
||||
"Language-Team: Turkish <tr@li.org>\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"
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import im_chat_session
|
||||
import res_users
|
||||
@@ -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 += '<a href="%s" target="_blank">%s</a>' % (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'''^<img\s+src=('|")([^'"]*)\1\s*/?>$''', 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)
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="im_chat_message_rule_user" model="ir.rule">
|
||||
<field name="name">Can only read messages that you sent or messages sent to you</field>
|
||||
<field name="model_id" ref="model_im_chat_message"/>
|
||||
<field name="groups" eval="[(4, ref('base.group_user')), (4, ref('base.group_portal'))]"/>
|
||||
<field name="domain_force">[('to_id.user_ids', 'in', [user.id])]</field>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_session_rule_user" model="ir.rule">
|
||||
<field name="name">Can only modify your session</field>
|
||||
<field name="model_id" ref="model_im_chat_session"/>
|
||||
<field name="groups" eval="[(4, ref('base.group_user')), (4, ref('base.group_portal'))]"/>
|
||||
<field name="domain_force">[('user_ids', 'in', [user.id])]</field>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_session_relation_rule_user" model="ir.rule">
|
||||
<field name="name">Can only modify your own session relations</field>
|
||||
<field name="model_id" ref="model_im_chat_conversation_state"/>
|
||||
<field name="groups" eval="[(4, ref('base.group_user')), (4, ref('base.group_portal'))]"/>
|
||||
<field name="domain_force">[('user_id', '=', user.id)]</field>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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
|
||||
|
|
Before Width: | Height: | Size: 20 KiB |
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,103 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="im_chat.Conversation">
|
||||
<div class="oe_im_chatview_header">
|
||||
<span class="header_name"></span>
|
||||
<span class="nbr_messages"/>
|
||||
<span class="right_buttons">
|
||||
<!-- options -->
|
||||
<div t-if="widget.actions.length !== 0" class="btn-group button_option_group">
|
||||
<span class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-cogs"></i>
|
||||
</span>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<t t-foreach="widget.actions" t-as="action">
|
||||
<li t-att-class="action.class">
|
||||
<i t-att-class="action.icon_class"></i> <t t-esc="action.label"/>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="oe_im_chatview_close">×</div>
|
||||
</span>
|
||||
</div>
|
||||
<div class="oe_im_chatview_content">
|
||||
<div class="oe_im_chatview_status"/>
|
||||
<div class="oe_im_chatview_content_bubbles"></div>
|
||||
</div>
|
||||
<div class="oe_im_chatview_footer">
|
||||
<input class="oe_im_chatview_input" t-att-placeholder="widget.inputPlaceholder" />
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="im_chat.Conversation_content">
|
||||
<t t-foreach="_.keys(list)" t-as="date">
|
||||
<div class="oe_im_chatview_date_separator">
|
||||
<t t-esc="window.moment(date).format('LL')"/>
|
||||
</div>
|
||||
<t t-foreach="list[date]" t-as="bubble">
|
||||
<t t-if="bubble[0].type === 'message'">
|
||||
<t t-call="im_chat.Conversation_message_bubble">
|
||||
<t t-set="messages" t-value="bubble"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="bubble[0].type === 'meta'">
|
||||
<t t-call="im_chat.Conversation_technical_bubble">
|
||||
<t t-set="messages" t-value="bubble"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="im_chat.Conversation_message_bubble">
|
||||
<div class="oe_im_chatview_message_bubble">
|
||||
<div class="oe_im_chatview_clip">
|
||||
<img class="oe_im_chatview_avatar" t-att-src="_.last(messages).from_id[2]"/>
|
||||
</div>
|
||||
<div class="oe_im_chatview_from"><t t-esc="_.last(messages).from_id[1]"/></div>
|
||||
<div class="oe_im_chatview_bubble_list">
|
||||
<t t-foreach="messages" t-as="m">
|
||||
<div class="oe_im_chatview_bubble_item"><t t-raw="m.message"/></div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="oe_im_chatview_time"><t t-esc="window.moment((_.last(messages).create_date)).format('LT')"/></div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="im_chat.Conversation_technical_bubble">
|
||||
<div class="oe_im_chatview_technical_bubble">
|
||||
<div class="oe_im_chatview_from"><t t-esc="_.last(messages).from_id[1]"/></div>
|
||||
<div>
|
||||
<t t-foreach="messages" t-as="m">
|
||||
<div><t t-raw="m.message"/></div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="oe_im_chatview_time"><t t-esc="window.moment((_.last(messages).create_date)).format('LT')"/></div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="im_chat.UserWidget">
|
||||
<div class="oe_im_user ui-draggable">
|
||||
<span class="oe_im_user_clip">
|
||||
<img t-att-src="widget.get('image_url')" class="oe_im_user_avatar"/>
|
||||
</span>
|
||||
<span class="oe_im_user_name"><t t-esc="widget.get('name')"/></span>
|
||||
<img t-att-src="'/im_chat/static/src/img/green.png'" t-att-data-im-user-id="widget.get('id')" class="oe_im_user_online"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="im_chat.InstantMessaging">
|
||||
<div class="oe_im">
|
||||
<div class="oe_im_frame_header">
|
||||
<i class="fa fa-search oe_im_search_icon"/>
|
||||
<input class="oe_im_searchbox" placeholder="Search users..."/>
|
||||
</div>
|
||||
<div class="oe_im_users"></div>
|
||||
<div class="oe_im_content"></div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="im_chat.ImTopButton">
|
||||
<li t-att-title='_t("Display Instant Messaging")'>
|
||||
<a href="#">
|
||||
<i id="oe_topbar_imbutton_icon" class="fa fa-comments"/>
|
||||
</a>
|
||||
</li>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<template id="assets_backend" name="im_chat assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/im_chat/static/src/css/im_chat_common.css"/>
|
||||
<link rel="stylesheet" href="/im_chat/static/src/css/im_chat_backend.css"/>
|
||||
<script type="text/javascript" src="/im_chat/static/src/js/im_chat_common.js"></script>
|
||||
<script type="text/javascript" src="/im_chat/static/src/js/im_chat_backend.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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,
|
||||
|
||||
@@ -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/<string:dbname>/<int:channel_id>', type='http', auth='none')
|
||||
def support_page(self, dbname, channel_id, **kwargs):
|
||||
Channel = request.env['im_livechat.channel']
|
||||
info = Channel.get_channel_infos(channel_id)
|
||||
info["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/<int:channel_id>', 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/<string:dbname>/<int:channel_id>', 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/<int:channel_id>', 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
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="model">im_chat.session</field>
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="function">cron_remove_empty_session</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
@@ -28,47 +28,20 @@
|
||||
</data>
|
||||
|
||||
<data>
|
||||
<!-- Custom Filters -->
|
||||
<record id="im_livechat_report_filter_session_by_day" model="ir.filters">
|
||||
<field name="name">Sessions by day</field>
|
||||
<field name="model_id">im_livechat.report</field>
|
||||
<field name="domain">[('nbr_speakers','>', 1)]</field>
|
||||
<field name="context">{'group_by': ['start_date:day', 'session_name']}</field>
|
||||
<field name="is_default">v</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="im_livechat_report_filter_session_last_24h" model="ir.filters">
|
||||
<field name="name">Last 24h stats</field>
|
||||
<field name="model_id">im_livechat.report</field>
|
||||
<field name="domain">[('start_date','>', (context_today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d') )]</field>
|
||||
<field name="context">{'group_by': ['start_date:day', 'session_name']}</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="im_livechat_report_filter_operator_stats" model="ir.filters">
|
||||
<field name="name">Operators stats</field>
|
||||
<field name="model_id">im_livechat.report</field>
|
||||
<field name="domain">[('nbr_speakers','>', 1), ('user_id', '!=', False)]</field>
|
||||
<field name="context">{'group_by': ['start_date:month', 'user_id', 'uuid']}</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Smiley for rating to be displayed in the conversation -->
|
||||
<record id="im_chat_shortcode_rating_0" model="im_chat.shortcode">
|
||||
<record id="mail_shortcode_rating_0" model="mail.shortcode">
|
||||
<field name="source">:rating_0</field>
|
||||
<field name="substitution"><img src='/rating/static/src/img/rating_0.png'/></field>
|
||||
<field name="description">Rating Smiley Bad</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_shortcode_rating_5" model="im_chat.shortcode">
|
||||
<record id="mail_shortcode_rating_5" model="mail.shortcode">
|
||||
<field name="source">:rating_5</field>
|
||||
<field name="substitution"><img src='/rating/static/src/img/rating_5.png'/></field>
|
||||
<field name="description">Rating Smiley Okay</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_shorcode_rating_10" model="im_chat.shortcode">
|
||||
<record id="mail_shorcode_rating_10" model="mail.shortcode">
|
||||
<field name="source">:rating_10</field>
|
||||
<field name="substitution"><img src='/rating/static/src/img/rating_10.png'/></field>
|
||||
<field name="description">Rating Smiley Good</field>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*
|
||||
import im_livechat_channel
|
||||
import im_chat_session
|
||||
import mail_channel
|
||||
import rating
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import im_livechat_report
|
||||
import im_livechat_report_channel
|
||||
import im_livechat_report_operator
|
||||
|
||||
@@ -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
|
||||
)
|
||||
""")
|
||||
@@ -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
|
||||
)
|
||||
""")
|
||||
@@ -2,42 +2,42 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="im_livechat_report_view_pivot" model="ir.ui.view">
|
||||
<field name="name">im_livechat.report.pivot</field>
|
||||
<field name="model">im_livechat.report</field>
|
||||
<record id="im_livechat_report_channel_view_pivot" model="ir.ui.view">
|
||||
<field name="name">im_livechat.report.channel.pivot</field>
|
||||
<field name="model">im_livechat.report.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Livechat Support Statistics" disable_linking="True">
|
||||
<field name="session_name" type="row"/>
|
||||
<field name="technical_name" type="row"/>
|
||||
<field name="duration" type="measure"/>
|
||||
<field name="nbr_messages" type="measure"/>
|
||||
<field name="time_to_answer" type="measure"/>
|
||||
<field name="nbr_message" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="im_livechat_report_view_graph" model="ir.ui.view">
|
||||
<field name="name">im_livechat.report.graph</field>
|
||||
<field name="model">im_livechat.report</field>
|
||||
<record id="im_livechat_report_channel_view_graph" model="ir.ui.view">
|
||||
<field name="name">im_livechat.report.channel.graph</field>
|
||||
<field name="model">im_livechat.report.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Livechat Support Statistics">
|
||||
<field name="session_name"/>
|
||||
<field name="nbr_messages" type="measure"/>
|
||||
<field name="technical_name"/>
|
||||
<field name="nbr_message" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="im_livechat_report_view_search" model="ir.ui.view">
|
||||
<field name="name">im_livechat.report.search</field>
|
||||
<field name="model">im_livechat.report</field>
|
||||
<record id="im_livechat_report_channel_view_search" model="ir.ui.view">
|
||||
<field name="name">im_livechat.report.channel.search</field>
|
||||
<field name="model">im_livechat.report.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search report">
|
||||
<filter name="missed_session" string="Missed sessions" domain="[('nbr_speakers','<=', 1)]"/>
|
||||
<filter name="treated_session" string="Treated sessions" domain="[('nbr_speakers','>', 1)]"/>
|
||||
<filter name="missed_session" string="Missed sessions" domain="[('nbr_speaker','<=', 1)]"/>
|
||||
<filter name="treated_session" string="Treated sessions" domain="[('nbr_speaker','>', 1)]"/>
|
||||
<filter name="last_24h" string="Last 24h" domain="[('start_date','>', (context_today() - datetime.timedelta(days=1)).strftime('%%Y-%%m-%%d') )]"/>
|
||||
<filter name="last_week" string="Last Week" domain="[('start_date','>', (context_today() - datetime.timedelta(weeks=1)).strftime('%%Y-%%m-%%d') )]"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter name="group_by_session" string="Session" domain="[]" context="{'group_by':'session_name'}"/>
|
||||
<filter name="group_by_channel" string="Channel" domain="[]" context="{'group_by':'channel_id'}"/>
|
||||
<filter name="group_by_user" string="Operator" domain="[('user_id','!=', False)]" context="{'group_by':'user_id'}"/>
|
||||
<filter name="group_by_operator" string="Operator" domain="[('partner_id','!=', False)]" context="{'group_by':'partner_id'}"/>
|
||||
<separator orientation="vertical" />
|
||||
<filter name="group_by_hour" string="Creation date (hour)" domain="[]" context="{'group_by':'start_date_hour'}"/>
|
||||
<filter name="group_by_day" string="Creation date (day)" domain="[]" context="{'group_by':'start_date:day'}"/>
|
||||
@@ -49,22 +49,29 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="im_livechat_report_action" model="ir.actions.act_window">
|
||||
<field name="name">Livechat Support Report</field>
|
||||
<field name="res_model">im_livechat.report</field>
|
||||
<record id="im_livechat_report_channel_action" model="ir.actions.act_window">
|
||||
<field name="name">Livechat Support Report Channel</field>
|
||||
<field name="res_model">im_livechat.report.channel</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">pivot,graph</field>
|
||||
<field name="help">Livechat Support Analysis allows you to easily check and analyse your company livechat session performance. Extract informations about the missed sessions, the audiance, the duration of a session, etc.</field>
|
||||
<field name="context">{"search_default_last_week":1}</field>
|
||||
<field name="help">Livechat Support Channel Statistics allows you to easily check and analyse your company livechat session performance. Extract informations about the missed sessions, the audiance, the duration of a session, etc.</field>
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem
|
||||
id="menu_reporting_livechat"
|
||||
name="Report"
|
||||
parent="menu_livechat_root"
|
||||
sequence="50"
|
||||
groups="im_livechat_group_manager"
|
||||
action="im_livechat_report_action"/>
|
||||
groups="im_livechat_group_manager"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_reporting_livechat_channel"
|
||||
name="Session Statistics"
|
||||
parent="menu_reporting_livechat"
|
||||
sequence="10"
|
||||
action="im_livechat_report_channel_action"/>
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp import fields, models, tools
|
||||
|
||||
|
||||
class ImLivechatReportOperator(models.Model):
|
||||
""" Livechat Support Report on the Operator """
|
||||
|
||||
_name = "im_livechat.report.operator"
|
||||
_description = "Livechat Support Report"
|
||||
_order = 'livechat_channel_id, partner_id'
|
||||
_auto = False
|
||||
|
||||
partner_id = fields.Many2one('res.partner', 'Operator', readonly=True)
|
||||
livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel', readonly=True)
|
||||
nbr_channel = fields.Integer('# of channel', readonly=True, group_operator="sum", help="Number of conversation")
|
||||
channel_id = fields.Many2one('mail.channel', 'Conversation', readonly=True)
|
||||
start_date = fields.Datetime('Start Date of session', readonly=True, help="Start date of the conversation")
|
||||
time_to_answer = fields.Float('Time to answer', digits=(16, 2), readonly=True, group_operator="avg", help="Average time to give the first answer to the visitor")
|
||||
duration = fields.Float('Average duration', digits=(16, 2), readonly=True, group_operator="avg", help="Duration of the conversation (in seconds)")
|
||||
|
||||
def init(self, cr):
|
||||
# Note : start_date_hour must be remove when the read_group will allow grouping on the hour of a datetime. Don't forget to change the view !
|
||||
tools.drop_view_if_exists(cr, 'im_livechat_report_operator')
|
||||
cr.execute("""
|
||||
CREATE OR REPLACE VIEW im_livechat_report_operator AS (
|
||||
SELECT
|
||||
row_number() OVER () AS id,
|
||||
P.id as partner_id,
|
||||
L.id as livechat_channel_id,
|
||||
count(C.id) as nbr_channel,
|
||||
C.id as channel_id,
|
||||
C.create_date as start_date,
|
||||
EXTRACT('epoch' FROM (max((SELECT (max(M.create_date)) FROM mail_message M JOIN mail_message_mail_channel_rel R ON (R.mail_message_id = M.id) WHERE R.mail_channel_id = C.id))-C.create_date)) as duration,
|
||||
EXTRACT('epoch' from ((SELECT min(M.create_date) FROM mail_message M, mail_message_mail_channel_rel R WHERE M.author_id=P.id AND R.mail_channel_id = C.id AND R.mail_message_id = M.id)-(SELECT min(M.create_date) FROM mail_message M, mail_message_mail_channel_rel R WHERE M.author_id IS NULL AND R.mail_channel_id = C.id AND R.mail_message_id = M.id))) as time_to_answer
|
||||
FROM im_livechat_channel_im_user O
|
||||
JOIN res_users U ON (O.user_id = U.id)
|
||||
JOIN res_partner P ON (U.partner_id = P.id)
|
||||
LEFT JOIN im_livechat_channel L ON (L.id = O.channel_id)
|
||||
LEFT JOIN mail_channel C ON (C.livechat_channel_id = L.id)
|
||||
GROUP BY P.id, L.id, C.id, C.create_date
|
||||
)
|
||||
""")
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="im_livechat_report_operator_view_pivot" model="ir.ui.view">
|
||||
<field name="name">im_livechat.report.operator.pivot</field>
|
||||
<field name="model">im_livechat.report.operator</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Livechat Support Statistics" disable_linking="True">
|
||||
<field name="partner_id" type="row"/>
|
||||
<field name="duration" type="measure"/>
|
||||
<field name="nbr_channel" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="im_livechat_report_operator_view_graph" model="ir.ui.view">
|
||||
<field name="name">im_livechat.report.operator.graph</field>
|
||||
<field name="model">im_livechat.report.operator</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Livechat Support Statistics">
|
||||
<field name="partner_id"/>
|
||||
<field name="nbr_channel" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="im_livechat_report_operator_view_search" model="ir.ui.view">
|
||||
<field name="name">im_livechat.report.operator.search</field>
|
||||
<field name="model">im_livechat.report.operator</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search report">
|
||||
<filter name="last_24h" string="Last 24h" domain="[('start_date','>', (context_today() - datetime.timedelta(days=1)).strftime('%%Y-%%m-%%d') )]"/>
|
||||
<filter name="last_week" string="Last Week" domain="[('start_date','>', (context_today() - datetime.timedelta(weeks=1)).strftime('%%Y-%%m-%%d') )]"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter name="group_by_channel" string="Channel" domain="[]" context="{'group_by':'channel_id'}"/>
|
||||
<filter name="group_by_operator" string="Operator" domain="[('partner_id','!=', False)]" context="{'group_by':'partner_id'}"/>
|
||||
<separator orientation="vertical" />
|
||||
<filter name="group_by_hour" string="Creation date (hour)" domain="[]" context="{'group_by':'start_date_hour'}"/>
|
||||
<filter name="group_by_day" string="Creation date (day)" domain="[]" context="{'group_by':'start_date:day'}"/>
|
||||
<filter name="group_by_week" string="Creation date (week)" domain="[]" context="{'group_by':'start_date:week'}"/>
|
||||
<filter name="group_by_month" string="Creation date (month)" domain="[]" context="{'group_by':'start_date:month'}" />
|
||||
<filter name="group_by_year" string="Creation date (year)" domain="[]" context="{'group_by':'start_date:year'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="im_livechat_report_operator_action" model="ir.actions.act_window">
|
||||
<field name="name">Livechat Support Report Channel</field>
|
||||
<field name="res_model">im_livechat.report.operator</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">pivot,graph</field>
|
||||
<field name="context">{"search_default_last_week":1}</field>
|
||||
<field name="help">Livechat Support Channel Statistics allows you to easily check and analyse your company livechat session performance. Extract informations about the missed sessions, the audiance, the duration of a session, etc.</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_reporting_livechat_operator"
|
||||
name="Operator Analysis"
|
||||
parent="menu_reporting_livechat"
|
||||
sequence="10"
|
||||
action="im_livechat_report_operator_action"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -21,22 +21,22 @@
|
||||
<field name="users" eval="[(4, ref('base.user_root'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_message_rule_manager" model="ir.rule">
|
||||
<record id="mail_message_rule_manager" model="ir.rule">
|
||||
<field name="name">Live Support Managers can read messages from live support</field>
|
||||
<field name="model_id" ref="im_chat.model_im_chat_message"/>
|
||||
<field name="model_id" ref="mail.model_mail_message"/>
|
||||
<field name="groups" eval="[(6,0,[ref('im_livechat.im_livechat_group_manager')])]"/>
|
||||
<field name="domain_force">[('to_id.channel_id', '!=', None)]</field>
|
||||
<field name="domain_force">[('mail.channel_ids.livechat_channel_id', '!=', None)]</field>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_create" eval="0"/>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_session_rule_manager" model="ir.rule">
|
||||
<record id="mail_channel_rule_manager" model="ir.rule">
|
||||
<field name="name">Live Support Managers can see all session from live support</field>
|
||||
<field name="model_id" ref="im_chat.model_im_chat_session"/>
|
||||
<field name="model_id" ref="mail.model_mail_channel"/>
|
||||
<field name="groups" eval="[(6,0,[ref('im_livechat.im_livechat_group_manager')])]"/>
|
||||
<field name="domain_force">[('channel_id', '!=', None)]</field>
|
||||
<field name="domain_force">[('mail.channel_ids.livechat_channel_id', '!=', None)]</field>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -2,28 +2,29 @@
|
||||
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="im_livechat.chatButton">
|
||||
<t t-esc="widget.text"/>
|
||||
<t t-name="im_livechat.LivechatButton">
|
||||
<div class="openerp o_livechat_button hidden-print" t-att-data-livechat-channel-id="widget.options.channel_id">
|
||||
<t t-esc="widget.options.button_text"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="im_livechat.feedback">
|
||||
<div class="oe_livechat_rating">
|
||||
<div class="oe_im_feedback_text">
|
||||
<div class="o_livechat_rating">
|
||||
<div class="o_livechat_rating_feedback_text">
|
||||
Did we correctly answer your question ?
|
||||
</div>
|
||||
<div class="oe_livechat_rating_choices">
|
||||
<div class="o_livechat_rating_choices">
|
||||
<img t-att-src="widget.server_origin + '/rating/static/src/img/rating_10.png'" alt="Good" data-value="10"/>
|
||||
<img t-att-src="widget.server_origin + '/rating/static/src/img/rating_5.png'" alt="OK" data-value="5"/>
|
||||
<img t-att-src="widget.server_origin + '/rating/static/src/img/rating_0.png'" alt="Bad" data-value="0" />
|
||||
</div>
|
||||
<div class="oe_livechat_rating_reason">
|
||||
<div class="o_livechat_rating_reason">
|
||||
<textarea id="reason" placeholder="Explain your note"></textarea>
|
||||
<div class="oe_livechat_rating_reason_button">
|
||||
<div class="o_livechat_rating_reason_button">
|
||||
<input type="button" id="rating_submit" value="Send" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
</templates>
|
||||
</templates>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
|
||||
<t t-extend="mail.chat.ChatMailThread.channels">
|
||||
<t t-jquery="li > a" t-operation="append">
|
||||
<t t-if="channel.channel_type === 'livechat'">
|
||||
<span class="fa fa-times pull-right o_mail_chat_channel_unpin" t-att-title="_t('Close')" t-att-data-channel-id="channel.id"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="mail.chat.ChatMailThread">
|
||||
<t t-jquery=".o_mail_chat_sidebar" t-operation="append">
|
||||
<!-- Livechat list -->
|
||||
<h4 class="o_mail_chat_sidebar_title"><t t-esc="_t('Livechat')"/></h4>
|
||||
<t t-call="mail.chat.ChatMailThread.channels">
|
||||
<t t-set="channel_slot" t-value="'channel_livechat'"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
@@ -2,8 +2,20 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Template rendering the external HTML support page -->
|
||||
<template id="support_page" name="Livechat Support Page">
|
||||
<!--
|
||||
Integrate Livechat Conversation in the Mail Chat Client Action
|
||||
-->
|
||||
<template id="assets_backend" name="im_livechat assets backend" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/im_livechat/static/src/js/im_livechat_backend.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
<!--
|
||||
Template rendering the external HTML support page
|
||||
-->
|
||||
<template id="support_page" name="Livechat : Support Page">
|
||||
<!DOCTYPE html>
|
||||
<html style="height: 100%">
|
||||
<head>
|
||||
@@ -12,7 +24,7 @@
|
||||
<title><t t-esc="channel_name"/> Livechat Support Page</title>
|
||||
|
||||
<!-- Call the external Bundle to render the css, js, and js loader tags -->
|
||||
<t t-call="im_livechat.external_loader"/>
|
||||
<t t-raw="channel.script_external"/>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
@@ -63,36 +75,32 @@
|
||||
|
||||
<body>
|
||||
<div class="main" style="opacity: 1;">
|
||||
<h1 class="channel_name"><t t-esc="channel_name"/></h1>
|
||||
<h1 class="channel_name"><t t-esc="channel.name"/></h1>
|
||||
<div>Live Chat Powered by <strong>Odoo</strong>.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Template rendering all the scripts required to execute the Livechat from an external page (which not contain Odoo) -->
|
||||
<template id="external_loader" name="All the scripts to launch the LiveSupport from an external Web Page">
|
||||
<!--
|
||||
Template rendering all the scripts required to execute the Livechat from an external page (which not contain Odoo)
|
||||
-->
|
||||
<template id="external_loader" name="Livechat : external_script field of livechat channel">
|
||||
<!-- css style -->
|
||||
<link t-att-href="'%s/web/css/im_livechat.external_lib' % (url)" rel="stylesheet"/>
|
||||
<!-- js of all the required lib (internal and external) -->
|
||||
<script t-att-src="'%s/web/js/im_livechat.external_lib' % (url)" type="text/javascript" async="async" />
|
||||
<script t-att-src="'%s/web/js/im_livechat.external_lib' % (url)" type="text/javascript"/>
|
||||
<!-- the loader -->
|
||||
<script t-att-src="'%s/im_livechat/loader/%s/%i' % (url, dbname, channel)" type="text/javascript" async="async" />
|
||||
<script t-att-src="'%s/im_livechat/loader/%i' % (url, channel_id)" type="text/javascript"/>
|
||||
</template>
|
||||
|
||||
<!-- Template rendering all the scripts required to execute the Livechat from a page containing Odoo -->
|
||||
<template id="internal_loader" name="All the scripts to launch the LiveSupport from an internal Web Page">
|
||||
<!-- css style -->
|
||||
<link t-att-href="'%s/web/css/im_livechat.internal_lib' % (url)" rel="stylesheet"/>
|
||||
<!-- js of all the required lib -->
|
||||
<script t-att-src="'%s/web/js/im_livechat.internal_lib' % (url)" type="text/javascript" />
|
||||
<!-- the loader -->
|
||||
<script t-att-src="'%s/im_livechat/loader/%s/%i' % (url, dbname, channel)" type="text/javascript" />
|
||||
</template>
|
||||
|
||||
<!-- Bundle of External Librairies of the Livechat -->
|
||||
<template id="external_lib" name="External Librairies of the Livechat, required to make it work">
|
||||
<!--
|
||||
Bundle of External Librairies of the Livechat (Odoo + required modules)
|
||||
-->
|
||||
<template id="external_lib" name="Livechat : External Librairies of the Livechat, required to make it work">
|
||||
<!-- Momentjs -->
|
||||
<script type="text/javascript" src="/web/static/lib/moment/moment.js"></script>
|
||||
<!-- OpenERP minimal lib -->
|
||||
<script type="text/javascript" src="/web/static/lib/underscore/underscore.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
|
||||
@@ -101,7 +109,6 @@
|
||||
<script type="text/javascript" src="/web/static/lib/jquery/jquery.browser.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/jquery.timeago/jquery.timeago.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
|
||||
|
||||
<!-- Qweb2 lib -->
|
||||
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
|
||||
<!-- Odoo JS Framework -->
|
||||
@@ -116,48 +123,46 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/framework/session.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/framework/utils.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/framework/core.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/framework/session_instance.js"></script>
|
||||
|
||||
<!-- add the internal lib -->
|
||||
<t t-call="im_livechat.internal_lib"/>
|
||||
</template>
|
||||
|
||||
<!-- Bundle of Librairies of the Bus, Chat, Livechat -->
|
||||
<template id="internal_lib" name="Librairies of the Livechat">
|
||||
<!-- Momentjs -->
|
||||
<script type="text/javascript" src="/web/static/lib/moment/moment.js"></script>
|
||||
<!-- IM module -->
|
||||
<!-- Bus, Mail, Livechat -->
|
||||
<script type="text/javascript" src="/bus/static/src/js/bus.js"></script>
|
||||
<script type="text/javascript" src="/im_chat/static/src/js/im_chat_common.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/js/mail_utils.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/js/mail_chat_common.js"></script>
|
||||
<script type="text/javascript" src="/im_livechat/static/src/js/im_livechat.js"></script>
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="/im_chat/static/src/css/im_chat_common.css"></link>
|
||||
<link rel="stylesheet" href="/mail/static/src/less/mail_chat_common.less"></link>
|
||||
<link rel="stylesheet" href="/im_livechat/static/src/css/im_livechat.css"/>
|
||||
</template>
|
||||
|
||||
<!-- the js code to initialize the LiveSupport object -->
|
||||
<template id="loader" name="Javascript initializing the LiveSupport">
|
||||
<t t-translate="off">
|
||||
$(function() {
|
||||
odoo.define('im_livechat.livesupport', function (require) {
|
||||
var im_livechat = require('im_livechat.im_livechat');
|
||||
window.livesupport = new im_livechat.LiveSupport(
|
||||
"<t t-esc="url"/>",
|
||||
"<t t-esc="dbname"/>",
|
||||
<t t-esc="channel"/>,
|
||||
{
|
||||
buttonText: "<t t-esc="buttonText"/>",
|
||||
inputPlaceholder: "<t t-esc="inputPlaceholder"/>",
|
||||
defaultMessage: "<t t-esc="defaultMessage"/>" || '',
|
||||
auto: window.oe_im_livechat_auto || false,
|
||||
defaultUsername: "<t t-esc="username"/>" || undefined,
|
||||
},
|
||||
<t t-raw="rule"/>
|
||||
);
|
||||
<template id="loader" name="Livechat : Javascript appending the livechat button">
|
||||
|
||||
<t t-if="not info">
|
||||
<t t-set="info" t-value="request.env['im_livechat.channel'].match_rules(request, channel)"/>
|
||||
</t>
|
||||
|
||||
<t t-if="info" t-translate="off">
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
|
||||
odoo.define('web.session', function (require) {
|
||||
var Session = require('web.Session');
|
||||
var modules = odoo._modules;
|
||||
return new Session(undefined, "<t t-esc="info['server_url']"/>", {modules:modules, use_cors: false});
|
||||
});
|
||||
|
||||
odoo.define('im_livechat.livesupport', function (require) {
|
||||
var im_livechat = require('im_livechat.im_livechat');
|
||||
var button = new im_livechat.LivechatButton(
|
||||
$('body'),
|
||||
"<t t-esc="info['server_url']"/>",
|
||||
<t t-raw="json.dumps(info.get('options', {}))"/>,
|
||||
<t t-raw="json.dumps(info.get('rule', False))"/>
|
||||
);
|
||||
button.appendTo($('body'));
|
||||
window.livechat_button = button;
|
||||
});
|
||||
});
|
||||
});
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<field name="web_page" widget="url"/>
|
||||
<field name="are_you_inside"/>
|
||||
<field name="user_ids"/>
|
||||
<field name="nbr_session"/>
|
||||
<field name="nbr_channel"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
@@ -44,7 +44,7 @@
|
||||
<t t-esc="(record.user_ids.raw_value || []).length"/> Operators
|
||||
<br/>
|
||||
<i class="fa fa-comments"></i>
|
||||
<t t-esc="record.nbr_session.raw_value"/> Sessions
|
||||
<t t-esc="record.nbr_channel.raw_value"/> Sessions
|
||||
</p>
|
||||
</div>
|
||||
<div class="o_kanban_button">
|
||||
@@ -66,8 +66,8 @@
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" type="action" attrs="{'invisible':[('nbr_session','=', 0)]}" name="%(im_chat_session_action_from_channel)d" icon="fa-comments">
|
||||
<field string="Sessions" name="nbr_session" widget="statinfo"/>
|
||||
<button class="oe_stat_button" type="action" attrs="{'invisible':[('nbr_channel','=', 0)]}" name="%(mail_channel_action_from_livechat_channel)d" icon="fa-comments">
|
||||
<field string="Sessions" name="nbr_channel" widget="statinfo"/>
|
||||
</button>
|
||||
<button name="action_view_rating" attrs="{'invisible':[('rating_percentage_satisfaction','=', -1)]}" class="oe_stat_button" type="object" icon="fa-smile-o">
|
||||
<field string="% Happy" name="rating_percentage_satisfaction" widget="statinfo"/>
|
||||
@@ -192,7 +192,7 @@
|
||||
id="session_history"
|
||||
name="History"
|
||||
parent="menu_livechat_root"
|
||||
action="im_chat_session_action"
|
||||
action="mail_channel_action"
|
||||
groups="im_livechat_group_user"
|
||||
sequence="10"/>
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="im_chat_session_view_search" model="ir.ui.view">
|
||||
<field name="name">im_chat.session.search</field>
|
||||
<field name="model">im_chat.session</field>
|
||||
<record id="mail_channel_view_search" model="ir.ui.view">
|
||||
<field name="name">mail.channel.search</field>
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search history">
|
||||
<filter name="id" string="My sessions" domain="[('user_ids','in', uid)]"/>
|
||||
@@ -20,28 +20,28 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_session_view_tree" model="ir.ui.view">
|
||||
<field name="name">im_chat.session.tree</field>
|
||||
<field name="model">im_chat.session</field>
|
||||
<record id="mail_channel_view_tree" model="ir.ui.view">
|
||||
<field name="name">mail.channel.tree</field>
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="History" create="false">
|
||||
<field name="id"/>
|
||||
<field name="fullname"/>
|
||||
<field name="name"/>
|
||||
<field name="create_date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_session_view_form" model="ir.ui.view">
|
||||
<field name="name">im_chat.session.form</field>
|
||||
<field name="model">im_chat.session</field>
|
||||
<record id="mail_channel_view_form" model="ir.ui.view">
|
||||
<field name="name">mail.channel.form</field>
|
||||
<field name="model">mail.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Session Form" create="false" edit="false">
|
||||
<sheet>
|
||||
<group>
|
||||
<group string="General">
|
||||
<field name="id"/>
|
||||
<field name="fullname" />
|
||||
<field name="name" />
|
||||
<field name="create_date" readonly="1"/>
|
||||
</group>
|
||||
<group string="Feedback">
|
||||
@@ -68,8 +68,8 @@
|
||||
<field name="message_ids" type="tree">
|
||||
<tree string="History">
|
||||
<field name="create_date" />
|
||||
<field name="from_id" />
|
||||
<field name="message" />
|
||||
<field name="author_id" />
|
||||
<field name="body" />
|
||||
</tree>
|
||||
</field>
|
||||
</sheet>
|
||||
@@ -78,25 +78,25 @@
|
||||
</record>
|
||||
|
||||
|
||||
<record id="im_chat_session_action" model="ir.actions.act_window">
|
||||
<record id="mail_channel_action" model="ir.actions.act_window">
|
||||
<field name="name">History</field>
|
||||
<field name="res_model">im_chat.session</field>
|
||||
<field name="res_model">mail.channel</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="domain">[('channel_id', '!=', None)]</field>
|
||||
<field name="context">{'group_by': ['channel_id', 'create_date:month']}</field>
|
||||
<field name="domain">[('livechat_channel_id', '!=', None)]</field>
|
||||
<field name="context">{'group_by': ['livechat_channel_id', 'create_date:month']}</field>
|
||||
</record>
|
||||
|
||||
<record id="im_chat_session_action_from_channel" model="ir.actions.act_window">
|
||||
<record id="mail_channel_action_from_livechat_channel" model="ir.actions.act_window">
|
||||
<field name="name">Sessions</field>
|
||||
<field name="res_model">im_chat.session</field>
|
||||
<field name="res_model">mail.channel</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="domain">[('channel_id', 'in', [active_id])]</field>
|
||||
<field name="domain">[('livechat_channel_id', 'in', [active_id])]</field>
|
||||
<field name="context">{
|
||||
'search_default_channel_id': [active_id],
|
||||
'default_channel_id': active_id,
|
||||
'search_default_livechat_channel_id': [active_id],
|
||||
'default_livechat_channel_id': active_id,
|
||||
}</field>
|
||||
<field name="search_view_id" ref="im_chat_session_view_search"/>
|
||||
<field name="search_view_id" ref="mail_channel_view_search"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
@@ -16,7 +16,7 @@ Ask your functionnal question directly to the Odoo Operators with the livechat s
|
||||
'data': [
|
||||
"views/im_odoo_support.xml"
|
||||
],
|
||||
'depends' : ["web", "im_chat"],
|
||||
'depends' : ["web", "mail"],
|
||||
'qweb': [
|
||||
'static/src/xml/im_odoo_support.xml'
|
||||
],
|
||||
|
||||
@@ -25,7 +25,7 @@ Main Features
|
||||
* Includes an extensible generic email composition assistant, that can turn into a mass-mailing assistant and is capable of interpreting simple *placeholder expressions* that will be replaced with dynamic data when each email is actually sent.
|
||||
""",
|
||||
'website': 'https://www.odoo.com/page/enterprise-social-network',
|
||||
'depends': ['base', 'base_setup'],
|
||||
'depends': ['base', 'base_setup', 'bus'],
|
||||
'data': [
|
||||
'wizard/invite_view.xml',
|
||||
'wizard/mail_compose_message_view.xml',
|
||||
@@ -34,11 +34,12 @@ Main Features
|
||||
'views/mail_message_views.xml',
|
||||
'views/mail_mail_views.xml',
|
||||
'views/mail_followers_views.xml',
|
||||
'views/mail_thread_views.xml',
|
||||
'views/mail_channel_views.xml',
|
||||
'views/mail_shortcode_views.xml',
|
||||
'views/res_config_views.xml',
|
||||
'data/mail_data.xml',
|
||||
'data/mail_channel_data.xml',
|
||||
'data/mail_shortcode_data.xml',
|
||||
'security/mail_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/mail_alias_views.xml',
|
||||
@@ -57,7 +58,10 @@ Main Features
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'qweb': [
|
||||
'static/src/xml/mail.xml',
|
||||
'static/src/xml/mail_thread.xml',
|
||||
'static/src/xml/mail_chatter.xml',
|
||||
'static/src/xml/mail_chat_common.xml',
|
||||
'static/src/xml/mail_chat_backend.xml',
|
||||
'static/src/xml/mail_followers.xml',
|
||||
'static/src/xml/announcement.xml',
|
||||
],
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
# -*- coding: utf-8 -*
|
||||
import main
|
||||
import bus
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*
|
||||
import base64
|
||||
import openerp
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.http import request
|
||||
|
||||
|
||||
class MailChatController(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.
|
||||
"""
|
||||
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:
|
||||
partner_id = request.env.user.partner_id.id
|
||||
|
||||
if partner_id:
|
||||
for mail_channel in request.env['mail.channel'].search([('channel_partner_ids', 'in', [partner_id])]):
|
||||
channels.append((request.db, 'mail.channel', mail_channel.id))
|
||||
# personal channel
|
||||
channels.append((request.db, 'res.partner', partner_id))
|
||||
return super(MailChatController, self)._poll(dbname, channels, last, options)
|
||||
|
||||
# --------------------------
|
||||
# Anonymous routes (Common Methods)
|
||||
# --------------------------
|
||||
@openerp.http.route('/mail/chat_post', type="json", auth="none")
|
||||
def mail_chat_post(self, uuid, message_content, **kwargs):
|
||||
request_uid = self._default_request_uid()
|
||||
# find the author from the user session, which can be None
|
||||
author_id = False # message_post accept 'False' author_id, but not 'None'
|
||||
if request.session.uid:
|
||||
author_id = request.env['res.users'].sudo().browse(request.session.uid).partner_id.id
|
||||
# post a message without adding followers to the channel. email_from=False avoid to get author from email data
|
||||
mail_channel = request.env["mail.channel"].sudo(request_uid).search([('uuid', '=', uuid)], limit=1)
|
||||
message = mail_channel.sudo(request_uid).with_context(mail_create_nosubscribe=True).message_post(author_id=author_id, email_from=False, body=message_content, message_type='comment', subtype='mail.mt_comment', **kwargs)
|
||||
return message and message.id or False
|
||||
|
||||
@openerp.http.route(['/mail/chat_history'], type="json", auth="none")
|
||||
def mail_chat_history(self, uuid, last_id=False, limit=20):
|
||||
request_uid = self._default_request_uid()
|
||||
channel = request.env["mail.channel"].sudo(request_uid).search([('uuid', '=', uuid)], limit=1)
|
||||
return channel.sudo(request_uid).channel_fetch_message(last_id, limit)
|
||||
|
||||
@openerp.http.route('/mail/chat_init', type="json", auth="none")
|
||||
def mail_chat_init(self):
|
||||
result = {
|
||||
'emoji': request.env['mail.shortcode'].sudo().search_read([('shortcode_type', '=', 'image')], ['source', 'substitution', 'description'])
|
||||
}
|
||||
# include the previous notifications, only for identified user
|
||||
if request.session.uid:
|
||||
request_uid = self._default_request_uid()
|
||||
result['notifications'] = request.env['mail.channel'].sudo(request_uid).get_init_notifications()
|
||||
return result
|
||||
@@ -205,3 +205,16 @@ class MailController(http.Controller):
|
||||
except:
|
||||
return self._redirect_to_messaging()
|
||||
return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id}))
|
||||
|
||||
@http.route('/mail/needaction', type='json', auth='user')
|
||||
def needaction(self):
|
||||
return request.env['res.partner'].get_needaction_count()
|
||||
|
||||
@http.route('/mail/client_action', type='json', auth='user')
|
||||
def mail_client_action(self):
|
||||
values = {
|
||||
'needaction_inbox_counter': request.env['res.partner'].get_needaction_count(),
|
||||
'chatter_needaction_auto': request.env.user.chatter_needaction_auto,
|
||||
'channel_slots': request.env['mail.channel'].channel_fetch_slot()
|
||||
}
|
||||
return values
|
||||
|
||||
@@ -41,17 +41,6 @@
|
||||
<field name="args">()</field>
|
||||
</record>
|
||||
|
||||
<!-- After installation of the module, open the related menu -->
|
||||
<record id="action_client_messaging_menu" model="ir.actions.client">
|
||||
<field name="name">Open Messaging Menu</field>
|
||||
<field name="tag">reload</field>
|
||||
<field name="params" eval="{'menu_id': ref('mail.mail_thread_menu_root')}"/>
|
||||
</record>
|
||||
<record id="base.open_menu" model="ir.actions.todo">
|
||||
<field name="action_id" ref="action_client_messaging_menu"/>
|
||||
<field name="state">open</field>
|
||||
</record>
|
||||
|
||||
<!-- Admin should not receivve emails at creation -->
|
||||
<record id="base.partner_root" model="res.partner">
|
||||
<field name="notify_email">none</field>
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Smileys -->
|
||||
<record id="mail_smiley_smile" model="mail.shortcode">
|
||||
<field name="source">:)</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/smile.png'/></field>
|
||||
<field name="description">Emoji Smile</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_sad" model="mail.shortcode">
|
||||
<field name="source">:(</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/sad.png'/></field>
|
||||
<field name="description">Emoji Sad</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_laugh" model="mail.shortcode">
|
||||
<field name="source">:D</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/laugh.png'/></field>
|
||||
<field name="description">Emoji Laugh</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_wink" model="mail.shortcode">
|
||||
<field name="source">;)</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/wink.png'/></field>
|
||||
<field name="description">Emoji Wink</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_stuck" model="mail.shortcode">
|
||||
<field name="source">O_0</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/stuck_out_tongue.png'/></field>
|
||||
<field name="description">Emoji Stuck Out Tongue Winking Eye</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_scream" model="mail.shortcode">
|
||||
<field name="source">:@</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/scream.png'/></field>
|
||||
<field name="description">Emoji Scream</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_neutral" model="mail.shortcode">
|
||||
<field name="source">:|</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/neutral.png'/></field>
|
||||
<field name="description">Emoji Speechless/Neutral face</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_cheeky" model="mail.shortcode">
|
||||
<field name="source">:p</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/yum.png'/></field>
|
||||
<field name="description">Emoji Cheeky</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_confused" model="mail.shortcode">
|
||||
<field name="source">:/</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/confused.png'/></field>
|
||||
<field name="description">Emoji Confused</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_open_mouth" model="mail.shortcode">
|
||||
<field name="source">:O</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/open_mouth.png'/></field>
|
||||
<field name="description">Emoji Open Mouth</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_heart" model="mail.shortcode">
|
||||
<field name="source">&lt;3</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/heart.png'/></field>
|
||||
<field name="description">Emoji Heart</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<!-- Other images -->
|
||||
<record id="mail_smiley_thumbsup" model="mail.shortcode">
|
||||
<field name="source">:+1</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/thumbsup.png'/></field>
|
||||
<field name="description">Emoji Thumbs Up</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_thumbsdown" model="mail.shortcode">
|
||||
<field name="source">:-1</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/thumbsdown.png'/></field>
|
||||
<field name="description">Emoji Thumbs Down</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_kitten" model="mail.shortcode">
|
||||
<field name="source">:kitten</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/cat.png'/></field>
|
||||
<field name="description">Emoji Kitten</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_pinky" model="mail.shortcode">
|
||||
<field name="source">:pinky</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/pinky.png'/></field>
|
||||
<field name="description">Emoji Pinky</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_musti" model="mail.shortcode">
|
||||
<field name="source">:musti</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/musti.png'/></field>
|
||||
<field name="description">Emoji Musti</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_man_with_turban" model="mail.shortcode">
|
||||
<field name="source">:turban</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/man_with_turban.png'/></field>
|
||||
<field name="description">Emoji Man With Turban</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_troll" model="mail.shortcode">
|
||||
<field name="source">:troll</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/trollface.png'/></field>
|
||||
<field name="description">Emoji Troll Face</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_boar" model="mail.shortcode">
|
||||
<field name="source">:boar</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/boar.png'/></field>
|
||||
<field name="description">Emoji Boar</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_poop" model="mail.shortcode">
|
||||
<field name="source">:poop</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/poop.png'/></field>
|
||||
<field name="description">Emoji Poop</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_cookie" model="mail.shortcode">
|
||||
<field name="source">:cookie</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/cookie.png'/></field>
|
||||
<field name="description">Emoji Cookie</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_postal_horn" model="mail.shortcode">
|
||||
<field name="source">:postal_horn</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/postal_horn.png'/></field>
|
||||
<field name="description">Emoji Postal Horn</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_ghost" model="mail.shortcode">
|
||||
<field name="source">:ghost</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/ghost.png'/></field>
|
||||
<field name="description">Emoji Ghost</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_beer" model="mail.shortcode">
|
||||
<field name="source">:beer</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/beer.png'/></field>
|
||||
<field name="description">Emoji Beer</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_smiley_man" model="mail.shortcode">
|
||||
<field name="source">:man</field>
|
||||
<field name="substitution"><img src='/mail/static/src/img/smiley/man.png'/></field>
|
||||
<field name="description">Emoji Man</field>
|
||||
<field name="shortcode_type">image</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -9,9 +9,10 @@ import mail_mail
|
||||
import mail_thread
|
||||
import mail_channel
|
||||
import mail_template
|
||||
import mail_shortcode
|
||||
import res_partner
|
||||
import res_users
|
||||
import res_config
|
||||
import update
|
||||
import ir_actions
|
||||
import html2text
|
||||
import html2text
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import datetime
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from openerp import _, api, fields, models, modules, tools
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
from openerp.addons.bus.models.bus_presence import AWAY_TIMER
|
||||
|
||||
|
||||
class ChannelPartner(models.Model):
|
||||
_name = 'mail.channel.partner'
|
||||
_description = 'Last Seen Many2many'
|
||||
_table = 'mail_channel_partner'
|
||||
_rec_name = 'partner_id'
|
||||
|
||||
partner_id = fields.Many2one('res.partner', string='Recipient')
|
||||
channel_id = fields.Many2one('mail.channel', string='Channel')
|
||||
seen_message_id = fields.Many2one('mail.message', string='Last Seen')
|
||||
fold_state = fields.Selection([('open', 'Open'), ('folded', 'Folded'), ('closed', 'Closed')], string='Conversation Fold State', default='open')
|
||||
is_minimized = fields.Boolean("Conversation is minimied")
|
||||
is_pinned = fields.Boolean("Is pinned on the interface", default=True)
|
||||
|
||||
|
||||
class Channel(models.Model):
|
||||
@@ -86,7 +95,7 @@ class Channel(models.Model):
|
||||
def create(self, vals):
|
||||
# Create channel and alias
|
||||
channel = super(Channel, self.with_context(
|
||||
alias_model_name=self._name, alias_parent_model_name=self._name, mail_create_nolog=True)
|
||||
alias_model_name=self._name, alias_parent_model_name=self._name, mail_create_nolog=True, mail_create_nosubscribe=True)
|
||||
).create(vals)
|
||||
channel.alias_id.write({"alias_force_thread_id": channel.id, 'alias_parent_thread_id': channel.id})
|
||||
|
||||
@@ -122,22 +131,19 @@ class Channel(models.Model):
|
||||
self._subscribe_users()
|
||||
return result
|
||||
|
||||
def _notify(self, message):
|
||||
# DO SOMETHING USEFULL
|
||||
return True
|
||||
|
||||
def _subscribe_users(self):
|
||||
for mail_channel in self:
|
||||
mail_channel.write({'channel_partner_ids': [(4, pid) for pid in mail_channel.mapped('group_ids').mapped('users').mapped('partner_id').ids]})
|
||||
|
||||
@api.multi
|
||||
def action_follow(self):
|
||||
return self.write({'channel_partner_ids': [(4, self.env.user.partner_id.id)]})
|
||||
return self.write({'channel_last_seen_partner_ids': [(0, 0, {'partner_id': self.env.user.partner_id.id})]})
|
||||
|
||||
@api.multi
|
||||
def action_unfollow(self):
|
||||
return self.write({'channel_partner_ids': [(3, self.env.user.partner_id.id)]})
|
||||
|
||||
|
||||
@api.multi
|
||||
def message_get_email_values(self, notif_mail=None):
|
||||
self.ensure_one()
|
||||
@@ -161,3 +167,327 @@ class Channel(models.Model):
|
||||
headers['X-Forge-To'] = list_to
|
||||
res['headers'] = repr(headers)
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def message_post(self, body='', subject=None, message_type='notification', subtype=None, parent_id=False, attachments=None, content_subtype='html', **kwargs):
|
||||
# auto pin 'direct_message' channel partner
|
||||
self.filtered(lambda channel: channel.channel_type == 'direct_message').mapped('channel_last_seen_partner_ids').write({'is_pinned': True})
|
||||
# apply shortcode (text only) subsitution
|
||||
body = self.env['mail.shortcode'].apply_shortcode(body, shortcode_type='text')
|
||||
message = super(Channel, self.with_context(mail_create_nosubscribe=True)).message_post(body=body, subject=subject, message_type=message_type, subtype=subtype, parent_id=parent_id, attachments=attachments, content_subtype=content_subtype, **kwargs)
|
||||
return message
|
||||
|
||||
#------------------------------------------------------
|
||||
# Instant Messaging API
|
||||
#------------------------------------------------------
|
||||
# A channel header should be broadcasted:
|
||||
# - when adding user to channel (only to the new added partners)
|
||||
# - when folding/minimizing a channel (only to the user making the action)
|
||||
# A message should be broadcasted:
|
||||
# - when a message is posted on a channel (to the channel, using _notify() method)
|
||||
|
||||
# Anonymous method
|
||||
@api.multi
|
||||
def _broadcast(self, partner_ids):
|
||||
""" Broadcast the current channel header to the given partner ids
|
||||
:param partner_ids : the partner to notify
|
||||
"""
|
||||
notifications = self._channel_channel_notifications(partner_ids)
|
||||
self.env['bus.bus'].sendmany(notifications)
|
||||
|
||||
@api.multi
|
||||
def _channel_channel_notifications(self, partner_ids):
|
||||
""" Generate the bus notifications of current channel for the given partner ids
|
||||
:param partner_ids : the partner to send the current channel header
|
||||
:returns list of bus notifications (tuple (bus_channe, message_content))
|
||||
"""
|
||||
notifications = []
|
||||
for partner in self.env['res.partner'].browse(partner_ids):
|
||||
user_id = partner.user_ids and partner.user_ids[0] or False
|
||||
if user_id:
|
||||
for channel_info in self.sudo(user_id).channel_info():
|
||||
notifications.append([(self._cr.dbname, 'res.partner', partner.id), channel_info])
|
||||
return notifications
|
||||
|
||||
@api.multi
|
||||
def _notify(self, message):
|
||||
""" Broadcast the given message on the current channels.
|
||||
Send the message on the Bus Channel (uuid for public mail.channel, and partner private bus channel (the tuple)).
|
||||
A partner will receive only on message on its bus channel, even if this message belongs to multiple mail channel. Then 'channel_ids' field
|
||||
of the received message indicates on wich mail channel the message should be displayed.
|
||||
:param : mail.message to broadcast
|
||||
"""
|
||||
message.ensure_one()
|
||||
notifications = self._channel_message_notifications(message)
|
||||
self.env['bus.bus'].sendmany(notifications)
|
||||
|
||||
@api.multi
|
||||
def _channel_message_notifications(self, message):
|
||||
""" Generate the bus notifications for the given message
|
||||
:param message : the mail.message to sent
|
||||
:returns list of bus notifications (tuple (bus_channe, message_content))
|
||||
"""
|
||||
message_values = message.message_format()[0]
|
||||
notifications = []
|
||||
for channel in self:
|
||||
message_values['channel_ids'] = [channel.id]
|
||||
notifications.append([(self._cr.dbname, 'mail.channel', channel.id), dict(message_values)])
|
||||
# add uuid to allow anonymous to listen
|
||||
if channel.public == 'public':
|
||||
notifications.append([channel.uuid, dict(message_values)])
|
||||
return notifications
|
||||
|
||||
@api.multi
|
||||
def channel_info(self):
|
||||
""" Get the informations header for the current channels
|
||||
:returns a list of channels values
|
||||
:rtype : list(dict)
|
||||
"""
|
||||
channel_infos = []
|
||||
partner_channels = self.env['mail.channel.partner']
|
||||
# find the channel partner state, if logged user
|
||||
if self.env.user and self.env.user.partner_id:
|
||||
partner_channels = self.env['mail.channel.partner'].search([('partner_id', '=', self.env.user.partner_id.id), ('channel_id', 'in', self.ids)])
|
||||
# for each channel, build the information header and include the logged partner information
|
||||
for channel in self:
|
||||
info = {
|
||||
'id': channel.id,
|
||||
'name': channel.name,
|
||||
'uuid': channel.uuid,
|
||||
'state': 'open',
|
||||
'is_minimized': False,
|
||||
'channel_type': channel.channel_type,
|
||||
'public': channel.public,
|
||||
}
|
||||
# add the partner for 'direct mesage' channel
|
||||
if channel.channel_type == 'chat':
|
||||
info['direct_partner'] = channel.sudo().channel_partner_ids.filtered(lambda p: p.id != self.env.user.partner_id.id).read(['id', 'name', 'im_status'])
|
||||
# add user session state, if available and if user is logged
|
||||
if partner_channels.ids:
|
||||
partner_channel = partner_channels.filtered(lambda c: channel.id == c.channel_id.id)
|
||||
if len(partner_channel) >= 1:
|
||||
partner_channel = partner_channel[0]
|
||||
info['state'] = partner_channel.fold_state or 'open'
|
||||
info['is_minimized'] = partner_channel.is_minimized
|
||||
info['seen_message_id'] = partner_channel.seen_message_id.id
|
||||
# add needaction and unread counter, since the user is logged
|
||||
info['message_needaction_counter'] = channel.message_needaction_counter
|
||||
info['message_unread_counter'] = channel.message_unread_counter
|
||||
channel_infos.append(info)
|
||||
return channel_infos
|
||||
|
||||
@api.multi
|
||||
def channel_fetch_message(self, last_id=False, limit=20):
|
||||
""" Return message values of the current channel.
|
||||
: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)
|
||||
"""
|
||||
self.ensure_one()
|
||||
domain = [("channel_ids", "in", self.ids)]
|
||||
if last_id:
|
||||
domain.append(("id", "<", last_id))
|
||||
return self.env['mail.message'].message_fetch(domain=domain, limit=limit)
|
||||
|
||||
# User methods
|
||||
@api.model
|
||||
def channel_get(self, partners_to, pin=True):
|
||||
""" Get the canonical private channel between some partners, create it if needed.
|
||||
To reuse an old channel (conversation), this one must be private, and contains
|
||||
only the given partners.
|
||||
:param partners_to : list of res.partner ids to add to the conversation
|
||||
:param pin : True if getting the channel should pin it for the current user
|
||||
:returns a channel header, or False if the users_to was False
|
||||
:rtype : dict
|
||||
"""
|
||||
if partners_to:
|
||||
partners_to.append(self.env.user.partner_id.id)
|
||||
# determine type according to the number of partner in the channel
|
||||
self.env.cr.execute("""
|
||||
SELECT P.channel_id as channel_id
|
||||
FROM mail_channel C, mail_channel_partner P
|
||||
WHERE P.channel_id = C.id
|
||||
AND C.public LIKE 'private'
|
||||
AND P.partner_id IN %s
|
||||
AND channel_type LIKE 'chat'
|
||||
GROUP BY P.channel_id
|
||||
HAVING COUNT(P.partner_id) = %s
|
||||
""", (tuple(partners_to), len(partners_to),))
|
||||
result = self.env.cr.dictfetchall()
|
||||
if result:
|
||||
# get the existing channel between the given partners
|
||||
channel = self.browse(result[0].get('channel_id'))
|
||||
# pin up the channel for the current partner
|
||||
if pin:
|
||||
self.env['mail.channel.partner'].search([('partner_id', '=', self.env.user.partner_id.id), ('channel_id', '=', channel.id)]).write({'is_pinned': True})
|
||||
else:
|
||||
# create a new one
|
||||
channel = self.create({
|
||||
'channel_last_seen_partner_ids': [(0, 0, {'partner_id': partner_id}) for partner_id in partners_to],
|
||||
'public': 'private',
|
||||
'channel_type': 'chat',
|
||||
'name': ', '.join(self.env['res.partner'].sudo().browse(partners_to).mapped('name')),
|
||||
})
|
||||
# broadcast the channel header to the other partner (not me)
|
||||
channel._broadcast(partners_to)
|
||||
return channel.channel_info()[0]
|
||||
return False
|
||||
|
||||
@api.model
|
||||
def channel_fold(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 state : the new status of the session for the current user.
|
||||
"""
|
||||
domain = [('partner_id', '=', self.env.user.partner_id.id), ('channel_id.uuid', '=', uuid)]
|
||||
for session_state in self.env['mail.channel.partner'].search(domain):
|
||||
if not state:
|
||||
state = session_state.fold_state
|
||||
if session_state.fold_state == 'open':
|
||||
state = 'folded'
|
||||
else:
|
||||
state = 'open'
|
||||
session_state.write({
|
||||
'fold_state': state,
|
||||
'is_minimized': bool(state != 'closed'),
|
||||
})
|
||||
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), session_state.channel_id.channel_info()[0])
|
||||
|
||||
@api.model
|
||||
def channel_minimize(self, uuid, minimized=True):
|
||||
values = {
|
||||
'fold_state': minimized and 'open' or 'closed',
|
||||
'is_minimized': minimized
|
||||
}
|
||||
domain = [('partner_id', '=', self.env.user.partner_id.id), ('channel_id.uuid', '=', uuid)]
|
||||
channel_partners = self.env['mail.channel.partner'].search(domain)
|
||||
channel_partners.write(values)
|
||||
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), channel_partners.channel_id.channel_info()[0])
|
||||
|
||||
@api.model
|
||||
def channel_pin(self, uuid, pinned=False):
|
||||
# add the person in the channel, and pin it (or unpin it)
|
||||
channel = self.search([('uuid', '=', uuid)])
|
||||
channel_partners = self.env['mail.channel.partner'].search([('partner_id', '=', self.env.user.partner_id.id), ('channel_id', '=', channel.id)])
|
||||
if channel_partners:
|
||||
channel_partners.write({'is_pinned': pinned})
|
||||
|
||||
@api.multi
|
||||
def channel_seen(self):
|
||||
self.ensure_one()
|
||||
if self.channel_message_ids.ids:
|
||||
last_message_id = self.channel_message_ids.ids[0] # zero is the index of the last message
|
||||
self.env['mail.channel.partner'].search([('channel_id', 'in', self.ids), ('partner_id', '=', self.env.user.partner_id.id)]).write({'seen_message_id': last_message_id})
|
||||
|
||||
@api.multi
|
||||
def channel_invite(self, partner_ids):
|
||||
""" Add the given partner_ids to the current channels and broadcast the channel header to them.
|
||||
:param partner_ids : list of partner id to add
|
||||
"""
|
||||
partners = self.env['res.partner'].browse(partner_ids)
|
||||
# add the partner
|
||||
for channel in self:
|
||||
partners_to_add = partners - channel.channel_partner_ids
|
||||
channel.write({'channel_last_seen_partner_ids': [(0, 0, {'partner_id': partner_id}) for partner_id in partners_to_add.ids]})
|
||||
# broadcast the channel header to the added partner
|
||||
self._broadcast(partner_ids)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Instant Messaging View Specific (Slack Client Action)
|
||||
#------------------------------------------------------
|
||||
@api.model
|
||||
def get_init_notifications(self):
|
||||
""" Get unread messages and old messages received less than AWAY_TIMER
|
||||
ago of minimized channel ONLY. This aims to set the minimized channel
|
||||
when refreshing the page.
|
||||
Note : the user need to be logged
|
||||
"""
|
||||
# get current user's minimzed channel
|
||||
minimized_channels = self.env['mail.channel.partner'].search([('is_minimized', '=', True), ('partner_id', '=', self.env.user.partner_id.id)]).mapped('channel_id')
|
||||
|
||||
# get the message since the AWAY_TIMER
|
||||
threshold = datetime.datetime.now() - datetime.timedelta(seconds=AWAY_TIMER)
|
||||
threshold = threshold.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
domain = [('channel_ids', 'in', minimized_channels.ids), ('create_date', '>', threshold)]
|
||||
|
||||
# get the message since the last poll of the user
|
||||
presence = self.env['bus.presence'].search([('user_id', '=', self._uid)], limit=1)
|
||||
if presence:
|
||||
domain.append(('create_date', '>', presence.last_poll))
|
||||
|
||||
# do the message search
|
||||
message_values = self.env['mail.message'].message_fetch(domain=domain)
|
||||
|
||||
# create the notifications (channel infos first, then messages)
|
||||
notifications = []
|
||||
for channel_info in minimized_channels.channel_info():
|
||||
notifications.append([(self._cr.dbname, 'res.partner', self.env.user.partner_id.id), channel_info])
|
||||
for message_value in message_values:
|
||||
for channel_id in message_value['channel_ids']:
|
||||
if channel_id in minimized_channels.ids:
|
||||
message_value['channel_ids'] = [channel_id]
|
||||
notifications.append([(self._cr.dbname, 'mail.channel', channel_id), dict(message_value)])
|
||||
return notifications
|
||||
|
||||
@api.model
|
||||
def channel_fetch_slot(self):
|
||||
""" Return the channels of the user grouped by 'slot' (channel, direct_message or private_group), and
|
||||
the mapping between partner_id/channel_id for direct_message channels.
|
||||
:returns dict : the grouped channels and the mapping
|
||||
"""
|
||||
values = {}
|
||||
my_partner_id = self.env.user.partner_id.id
|
||||
pinned_channels = self.env['mail.channel.partner'].search([('partner_id', '=', my_partner_id), ('is_pinned', '=', True)]).mapped('channel_id')
|
||||
|
||||
# get the group/public channels
|
||||
values['channel_channel'] = self.search([('channel_type', '=', 'channel'), ('public', 'in', ['public', 'groups']), ('channel_partner_ids', 'in', [my_partner_id])]).channel_info()
|
||||
|
||||
# get the pinned 'direct message' channel
|
||||
direct_message_channels = self.search([('channel_type', '=', 'chat'), ('id', 'in', pinned_channels.ids)])
|
||||
values['channel_direct_message'] = direct_message_channels.channel_info()
|
||||
|
||||
# get the partner of the pinned 'direct message' channels with mapping partner/channel
|
||||
direct_channel_partner = direct_message_channels.mapped('channel_last_seen_partner_ids').filtered(lambda cp: cp.partner_id.id != my_partner_id)
|
||||
values['mapping'] = dict((item.partner_id.id, item.channel_id.id) for item in direct_channel_partner)
|
||||
# partners of the 'direct message' channels
|
||||
values['partners'] = direct_message_channels.mapped('channel_partner_ids').filtered(lambda partner: partner.id != my_partner_id).read(['id', 'name', 'im_status'])
|
||||
|
||||
# get the private group
|
||||
values['channel_private_group'] = self.search([('channel_type', '=', 'channel'), ('public', '=', 'private'), ('channel_partner_ids', 'in', [my_partner_id])]).channel_info()
|
||||
return values
|
||||
|
||||
@api.model
|
||||
def channel_search_to_join(self, name=None, domain=None):
|
||||
""" Return the channel info of the channel the current partner can join
|
||||
:param name : the name of the researched channels
|
||||
:param domain : the base domain of the research
|
||||
:returns dict : channel dict
|
||||
"""
|
||||
if not domain:
|
||||
domain = []
|
||||
domain += [('channel_type', '=', 'channel'), ('channel_partner_ids', 'not in', [self.env.user.partner_id.id])]
|
||||
if name:
|
||||
domain.append(('name', 'ilike', '%'+name+'%'))
|
||||
return self.search(domain).read(['name', 'public', 'uuid', 'channel_type'])
|
||||
|
||||
@api.multi
|
||||
def channel_join_and_get_info(self):
|
||||
self.ensure_one()
|
||||
self.action_follow()
|
||||
return self.channel_info()[0]
|
||||
|
||||
@api.model
|
||||
def channel_create(self, name, privacy='public'):
|
||||
""" Create a channel and add the current partner, broadcast it (to make the user directly
|
||||
listen to it when polling)
|
||||
:param name : the name of the channel to create
|
||||
:param privacy : privacy of the channel. Should be 'public' or 'private'.
|
||||
:return dict : channel header
|
||||
"""
|
||||
# create the channel
|
||||
new_channel = self.create({'name': name, 'public': privacy})
|
||||
new_channel.action_follow()
|
||||
return new_channel.channel_info()[0]
|
||||
|
||||
@@ -248,12 +248,12 @@ class Message(models.Model):
|
||||
tracking_value_ids.append(tracking_tree[tracking_value.id])
|
||||
|
||||
message_dict.update({
|
||||
'is_author': pid == author[0],
|
||||
#'is_author': pid == author[0],
|
||||
'author_id': author,
|
||||
'partner_ids': partner_ids,
|
||||
'attachment_ids': attachment_ids,
|
||||
'tracking_value_ids': tracking_value_ids,
|
||||
'user_pid': pid
|
||||
#'user_pid': pid
|
||||
})
|
||||
|
||||
return True
|
||||
@@ -281,7 +281,8 @@ class Message(models.Model):
|
||||
'record_name': self.record_name,
|
||||
'subject': self.subject,
|
||||
'date': self.date,
|
||||
'needaction': self.needaction,
|
||||
#'needaction': self.needaction, JEM : not used.
|
||||
'needaction_partner_ids': self.needaction_partner_ids.ids,
|
||||
'parent_id': parent_id,
|
||||
'is_private': is_private,
|
||||
'author_id': False,
|
||||
@@ -291,12 +292,13 @@ class Message(models.Model):
|
||||
'is_favorite': self.starred,
|
||||
'attachment_ids': [],
|
||||
'tracking_value_ids': [],
|
||||
'channel_ids': self.channel_ids.ids,
|
||||
}
|
||||
|
||||
@api.cr_uid_context
|
||||
def message_read_wrapper(self, cr, uid, ids=None, domain=None, context=None,
|
||||
thread_level=0, parent_id=False, limit=None, child_limit=None):
|
||||
return self.message_read(cr, uid, ids, domain=domain, thread_level=thread_level, context=context,
|
||||
return self.message_read(cr, uid, ids, domain=domain, thread_level=thread_level, context=context,
|
||||
parent_id=parent_id, limit=limit, child_limit=child_limit)
|
||||
|
||||
@api.multi
|
||||
@@ -308,7 +310,7 @@ class Message(models.Model):
|
||||
well formed threads, if uid has access to them.
|
||||
|
||||
After reading the messages, expandable messages are added in the
|
||||
message list. It consists in messages holding the 'read more' data:
|
||||
message list. It consists in messages holding the 'read more' data:
|
||||
number of messages to read, domain to apply.
|
||||
|
||||
:param list ids: optional IDs to fetch
|
||||
@@ -320,7 +322,7 @@ class Message(models.Model):
|
||||
:param int limit: number of messages to fetch, before adding the
|
||||
ancestors and expandables
|
||||
:param int child_limit: number of child messages to fetch
|
||||
:return dict:
|
||||
:return dict:
|
||||
- int: number of messages read (status 'unread' to 'read')
|
||||
- list: list of threads [[messages_of_thread1], [messages_of_thread2]]
|
||||
"""
|
||||
@@ -397,7 +399,7 @@ class Message(models.Model):
|
||||
'nb_messages': more_count,
|
||||
'parent_id': parent}
|
||||
|
||||
if parent and thread_level:
|
||||
if parent and thread_level:
|
||||
#insert expandable before parent message
|
||||
parent_tree[parent].insert(len(parent_tree[parent])-1, exp)
|
||||
else:
|
||||
@@ -408,7 +410,7 @@ class Message(models.Model):
|
||||
parent_list = parent_tree.values()
|
||||
parent_list = sorted(parent_list, key=lambda item: max([msg.get('id') for msg in item]), reverse=True)
|
||||
|
||||
#add 'thread' expandable
|
||||
#add 'thread' expandable
|
||||
if thread_level:
|
||||
exp_domain = domain + [('id', '<', min(self.ids)), ('id', 'not in', parent_ids), ('parent_id', 'not in', parent_ids)]
|
||||
more_count = self.search_count(exp_domain)
|
||||
@@ -419,15 +421,79 @@ class Message(models.Model):
|
||||
'parent_id': parent_id}])
|
||||
|
||||
nb_read = 0
|
||||
if context and 'mail_read_set_read' in context and context['mail_read_set_read']:
|
||||
if context and 'mail_read_set_read' in context and context['mail_read_set_read']:
|
||||
nb_read = self.set_message_read(True, create_missing=False)
|
||||
|
||||
else:
|
||||
nb_read = 0
|
||||
parent_list = []
|
||||
parent_list = []
|
||||
|
||||
return {'nb_read': nb_read, 'threads': parent_list}
|
||||
|
||||
|
||||
@api.model
|
||||
def message_fetch(self, domain, limit=20):
|
||||
return self.search(domain, limit=limit).message_format()
|
||||
|
||||
@api.multi
|
||||
def message_format(self):
|
||||
""" Get the message values in the format for web client. Since message values can be broadcasted,
|
||||
computed fields MUST NOT BE READ and broadcasted.
|
||||
:returns list(dict).
|
||||
Example :
|
||||
{
|
||||
'body': HTML content of the message
|
||||
'model': u'res.partner',
|
||||
'record_name': u'Agrolait',
|
||||
'attachment_ids': [
|
||||
{
|
||||
'file_type_icon': u'webimage',
|
||||
'id': 45,
|
||||
'name': u'sample.png',
|
||||
'filename': u'sample.png'
|
||||
}
|
||||
],
|
||||
'needaction_partner_ids': [], # list of partner ids
|
||||
'res_id': 7,
|
||||
'tracking_value_ids': [
|
||||
{
|
||||
'old_value': "",
|
||||
'changed_field': "Customer",
|
||||
'id': 2965,
|
||||
'new_value': "Axelor"
|
||||
}
|
||||
],
|
||||
'author_id': (3, u'Administrator'),
|
||||
'email_from': 'sacha@pokemon.com' # email address or False
|
||||
'subtype_id': (1, u'Discussions'),
|
||||
'channel_ids': [], # list of channel ids
|
||||
'date': '2015-06-30 08:22:33',
|
||||
'partner_ids': [[7, "Sacha Du Bourg-Palette"]], # list of partner name_get
|
||||
'message_type': u'comment',
|
||||
'id': 59,
|
||||
'subject': False
|
||||
'is_note': True # only if the subtype is internal
|
||||
}
|
||||
"""
|
||||
message_values = self.read([
|
||||
'id', 'body', 'date', 'author_id', 'email_from', # base message fields
|
||||
'message_type', 'subtype_id', 'subject', # message specific
|
||||
'model', 'res_id', 'record_name', # document related
|
||||
'channel_ids', 'partner_ids', # recipients
|
||||
'needaction_partner_ids', # list of partner ids for whom the message is a needaction
|
||||
'starred_partner_ids', # list of partner ids for whom the message is starred
|
||||
])
|
||||
message_tree = dict((m.id, m) for m in self)
|
||||
self._message_read_dict_postprocess(message_values, message_tree)
|
||||
|
||||
# add is_note flag
|
||||
internal_subtype_ids = self.env['mail.message.subtype'].search([('internal', '=', True)]).ids
|
||||
for message in message_values:
|
||||
if message['subtype_id'] and message['subtype_id'][0]:
|
||||
message['is_note'] = bool(message['subtype_id'][0] in internal_subtype_ids)
|
||||
return message_values
|
||||
|
||||
|
||||
#------------------------------------------------------
|
||||
# mail_message internals
|
||||
#------------------------------------------------------
|
||||
@@ -750,7 +816,9 @@ class Message(models.Model):
|
||||
self_sudo = self.sudo()
|
||||
|
||||
# TDE CHECK: add partners / channels as arguments to be able to notify a message with / without computation ??
|
||||
self.ensure_one() # tde: not sure, just for testing, will see
|
||||
self.ensure_one() # tde: not sure, just for testinh, will see
|
||||
partners = self.env['res.partner'] | self.partner_ids
|
||||
channels = self.env['mail.channel'] | self.channel_ids
|
||||
|
||||
# all followers of the mail.message document have to be added as partners and notified
|
||||
# and filter to employees only if the subtype is internal
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import cgi
|
||||
import re
|
||||
|
||||
from openerp import api, fields, models
|
||||
|
||||
|
||||
class MailShortcode(models.Model):
|
||||
""" Shortcode
|
||||
Canned Responses, allowing the user to defined shortcuts in its message. Should be applied before storing message in database.
|
||||
Emoji allowing replacing text with image for visual effect. Should be applied when the message is displayed (only for final rendering).
|
||||
These shortcodes are global and are available for every user.
|
||||
"""
|
||||
|
||||
_name = 'mail.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')
|
||||
shortcode_type = fields.Selection([('image', 'Smiley'), ('text', 'Canned Response')], required=True, default='text',
|
||||
help="* Smiley are only used for HTML code to display an image "\
|
||||
"* Text (default value) is use to substitute text with another text")
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
if values.get('substitution'):
|
||||
values['substitution'] = self._sanitize_shorcode(values['substitution'])
|
||||
return super(MailShortcode, self).create(values)
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
if values.get('substitution'):
|
||||
values['substitution'] = self._sanitize_shorcode(values['substitution'])
|
||||
return super(MailShortcode, self).write(values)
|
||||
|
||||
def _sanitize_shorcode(self, substitution):
|
||||
""" Sanitize the shortcode substitution :
|
||||
- HTML substitution : only allow the img tag (emoji)
|
||||
- escape other substitutions to avoid XSS
|
||||
"""
|
||||
is_img_tag = re.match(r'''^<img\s+src=('|")([^'"]*)\1\s*/?>$''', substitution, re.M | re.I)
|
||||
if is_img_tag:
|
||||
return substitution
|
||||
return cgi.escape(substitution)
|
||||
|
||||
@api.model
|
||||
def apply_shortcode(self, message, shortcode_type=None):
|
||||
""" Apply the substitution on the given text
|
||||
:param message : the text where the shortcode will be applied
|
||||
:param shortcode_type : shortcode type that should be applied during the substitution
|
||||
"""
|
||||
domain = []
|
||||
if shortcode_type:
|
||||
domain.append(('shortcode_type', '=', shortcode_type))
|
||||
for shortcode in self.search(domain):
|
||||
regex = '(?:^|\s)(%s)(?:\s|$)' % re.escape(shortcode.source)
|
||||
message = re.sub(regex, " " + shortcode.substitution + " ", message)
|
||||
return message
|
||||
@@ -346,6 +346,9 @@ class MailThread(models.AbstractModel):
|
||||
('res_model', 'in', [False, self._name]),
|
||||
('internal', '=', True)], ['name', 'description', 'sequence'])
|
||||
options['internal_subtypes'] = internal_subtypes
|
||||
# emoji list
|
||||
options['emoji_list'] = self.env['mail.shortcode'].search([('shortcode_type', '=', 'image')]).read(['source', 'description', 'substitution'])
|
||||
# save options on the node
|
||||
node.set('options', json.dumps(options))
|
||||
res['arch'] = etree.tostring(doc)
|
||||
return res
|
||||
@@ -501,7 +504,7 @@ class MailThread(models.AbstractModel):
|
||||
""" When redirecting towards the Inbox, choose which action xml_id has
|
||||
to be fetched. This method is meant to be inherited, at least in portal
|
||||
because portal users have a different Inbox action than classic users. """
|
||||
return 'mail.mail_message_action_inbox'
|
||||
return 'mail.mail_channel_action_client_chat'
|
||||
|
||||
@api.multi
|
||||
def _notification_link_helper(self, link_type, **kwargs):
|
||||
@@ -1855,9 +1858,9 @@ class MailThread(models.AbstractModel):
|
||||
|
||||
@api.multi
|
||||
def message_set_read(self):
|
||||
# Nothing done here currently. Will be implemented with the final
|
||||
# slack modeling.
|
||||
return True
|
||||
messages = self.env['mail.message'].search([('model', '=', self._name), ('res_id', 'in', self.ids), ('needaction', '=', True)])
|
||||
messages.write({'needaction_partner_ids': [(3, self.env.user.partner_id.id)]})
|
||||
return messages.ids
|
||||
|
||||
@api.multi
|
||||
def message_change_thread(self, new_thread):
|
||||
|
||||
@@ -173,3 +173,12 @@ class Partner(models.Model):
|
||||
emails.send()
|
||||
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def get_needaction_count(self):
|
||||
""" compute the number of needaction of the current user """
|
||||
self.env.cr.execute("""
|
||||
SELECT count(*) as needaction_count
|
||||
FROM mail_message_res_partner_needaction_rel R
|
||||
WHERE R.res_partner_id = %s """, (self.env.user.partner_id.id,))
|
||||
return self.env.cr.dictfetchall()[0].get('needaction_count')
|
||||
|
||||
@@ -27,4 +27,6 @@ access_mail_tracking_value_system,mail.tracking.value.system,model_mail_tracking
|
||||
access_mail_thread_all,mail.thread.all,model_mail_thread,,1,1,1,1
|
||||
access_publisher_warranty_contract_all,publisher.warranty.contract.all,model_publisher_warranty_contract,,1,1,1,1
|
||||
access_mail_template,mail.template,model_mail_template,base.group_user,1,1,1,0
|
||||
access_mail_template_system,mail.template_system,model_mail_template,base.group_system,1,1,1,1
|
||||
access_mail_template_system,mail.template_system,model_mail_template,base.group_system,1,1,1,1
|
||||
access_mail_shortcode,mail.shortcode,model_mail_shortcode,base.group_user,1,1,1,1
|
||||
access_mail_shortcode_portal,mail.shortcode.portal,model_mail_shortcode,base.group_portal,1,0,0,0
|
||||
|
||||
|
@@ -1,104 +0,0 @@
|
||||
/* ------------------------------ */
|
||||
/* Group Form */
|
||||
/* ------------------------------ */
|
||||
|
||||
/* Resize sheet width */
|
||||
.o_web_client .oe_form_sheetbg.o_web_client_mail_channel_sheet {
|
||||
min-height: 0px;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.o_web_client .oe_form_sheetbg.o_web_client_mail_channel_sheet .oe_form_sheet {
|
||||
min-height: 0px;
|
||||
max-height: none;
|
||||
padding: 0px 18px;
|
||||
}
|
||||
|
||||
.o_web_client .oe_form_sheetbg.o_web_client_mail_channel_sheet .oe_avatar {
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
/* Resize footer width */
|
||||
.o_web_client .oe_form div.oe_mail_channel_footer {
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
/* Resize group description */
|
||||
.o_web_client .oe_form_sheetbg.o_web_client_mail_channel_sheet .oe_form_field_text > textarea {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
/* ------------------------------ */
|
||||
/* Group Kanban */
|
||||
/* ------------------------------ */
|
||||
|
||||
.oe_group_vignette {
|
||||
padding: 8px 0;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.oe_group_image, .oe_group_details {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.oe_group_image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
-o-border-radius: 3px;
|
||||
-ms-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
border-collapse: separate;
|
||||
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
-o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.oe_group_photo {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
clip: rect(0px, 100px, 100px, 0px);
|
||||
border:none;
|
||||
}
|
||||
|
||||
.oe_group_details {
|
||||
width: 220px;
|
||||
font-size: 13px;
|
||||
padding: 2px 5px;
|
||||
color: #4c4c4c;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.oe_group_details h4 {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.oe_group_details ul {
|
||||
margin: 3px 0 5px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.o_web_client .oe_group_details li {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.o_web_client .oe_group_button {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.o_web_client .oe_group_button .oe_group_join {
|
||||
color: white;
|
||||
background-color: #3465A4;
|
||||
background-image: -webkit-linear-gradient(top, #729FCF, #3465A4);
|
||||
background-image: -moz-linear-gradient(top, #729FCF, #3465A4);
|
||||
background-image: -ms-linear-gradient(top, #729FCF, #3465A4);
|
||||
background-image: -o-linear-gradient(top, #729FCF, #3465A4);
|
||||
background-image: linear-gradient(to bottom, #729FCF, #3465A4);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |