diff --git a/bus_presence_override/README.rst b/bus_presence_override/README.rst new file mode 100644 index 00000000..2fd99a8d --- /dev/null +++ b/bus_presence_override/README.rst @@ -0,0 +1,52 @@ +.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +===================== +Bus Presence Override +===================== + +This module adds the ability for users to define their Online, Away, or Offline status +manually instead of the system calculating it. + +Away and disconnection timers are still in effect. + +Usage +===== + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/205/10.0 + +Known Issues / Roadmap +====================== + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Brett Wood + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. diff --git a/bus_presence_override/__init__.py b/bus_presence_override/__init__.py new file mode 100644 index 00000000..fd02263c --- /dev/null +++ b/bus_presence_override/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import models diff --git a/bus_presence_override/__manifest__.py b/bus_presence_override/__manifest__.py new file mode 100644 index 00000000..463de09f --- /dev/null +++ b/bus_presence_override/__manifest__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +{ + "name": "Bus Presence Override", + "summary": "Adds user-defined im status (online, away, offline).", + "version": "10.0.1.0.0", + "category": "Social", + "website": "https://github.com/OCA/social", + "author": "LasLabs, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "mail", + ], + "data": [ + "views/assets.xml", + ], + "qweb": [ + "static/src/xml/systray.xml", + ], +} diff --git a/bus_presence_override/models/__init__.py b/bus_presence_override/models/__init__.py new file mode 100644 index 00000000..77c339cb --- /dev/null +++ b/bus_presence_override/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import res_partner diff --git a/bus_presence_override/models/res_partner.py b/bus_presence_override/models/res_partner.py new file mode 100644 index 00000000..732912e3 --- /dev/null +++ b/bus_presence_override/models/res_partner.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + +from odoo.addons.bus.models.bus_presence import AWAY_TIMER +from odoo.addons.bus.models.bus_presence import DISCONNECTION_TIMER + +from ..status_constants import ONLINE, AWAY, OFFLINE + + +class ResPartner(models.Model): + + _inherit = 'res.partner' + + im_status_custom = fields.Selection( + selection=[ + (ONLINE, 'Online'), + (AWAY, 'Away'), + (OFFLINE, 'Offline'), + ], + string='Status', + default=ONLINE, + ) + + @api.model + def _get_partners_presence(self): + """ This method gets any relevant partners' im status. + + Example: + + .. code-block:: python + + res = self.env['res.partner']._get_partners_presence() + im_status = res.get(partner.id, OFFLINE) + + Returns: + dict: Structured like so: {partner_id: im_status} + + """ + self.env.cr.execute( + """ + SELECT + U.partner_id as id, + CASE WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) + > interval %s THEN 'offline' + WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) + > interval %s THEN 'away' + ELSE 'online' + END as status + FROM bus_presence B + JOIN res_users U ON B.user_id = U.id + WHERE U.partner_id IN %s AND U.active = 't' + """, + ("%s seconds" % DISCONNECTION_TIMER, + "%s seconds" % AWAY_TIMER, tuple(self.ids)) + ) + all_status = self.env.cr.dictfetchall() + all_status_dict = dict( + ((status['id'], status['status']) for status in all_status) + ) + return all_status_dict + + @api.multi + def _compute_im_status(self): + presence = self._get_partners_presence() + + for record in self: + + record.im_status = presence.get(record.id, OFFLINE) + + computed = record.im_status + custom = record.im_status_custom + + if computed == OFFLINE: + record.im_status_custom = computed + + elif custom in (AWAY, OFFLINE): + record.im_status = custom + + elif computed == AWAY and custom == ONLINE: + record.im_status_custom = computed diff --git a/bus_presence_override/static/description/icon.png b/bus_presence_override/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/bus_presence_override/static/description/icon.png differ diff --git a/bus_presence_override/static/src/js/systray.js b/bus_presence_override/static/src/js/systray.js new file mode 100644 index 00000000..43599cc0 --- /dev/null +++ b/bus_presence_override/static/src/js/systray.js @@ -0,0 +1,59 @@ +/* Copyright 2017 LasLabs Inc. + License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ + +odoo.define('bus_presence_override.systray', function (require) { + "use strict"; + + var DataModel = require('web.DataModel'); + var session = require('web.session'); + var SystrayMenu = require('web.SystrayMenu'); + var Widget = require('web.Widget'); + + var Systray = Widget.extend({ + template:'systray', + events: { + 'click .o_user_presence_status': 'on_click_user_presence_status', + }, + init: function() { + this._super.apply(this, arguments); + this.Partners = new DataModel('res.partner'); + this.status_icons = { + 'online': 'fa fa-circle o_user_online', + 'away': 'fa fa-circle o_user_idle', + 'offline': 'fa fa-circle-o', + } + }, + start: function () { + this._update_im_status_custom(status='online'); + return this._super() + }, + on_click_user_presence_status: function (event) { + var status = $(event.target).attr('name'); + this._update_im_status_custom(status); + }, + _get_im_status: function () { + var self = this; + this.Partners.query(['im_status']) + .filter([['id', '=', session.partner_id]]) + .first() + .then(function (result) { + self._update_systray_status_icon(result['im_status']); + }); + }, + _update_systray_status_icon: function (status) { + $('#userStatus i').removeClass() + .addClass('o_mail_user_status ' + this.status_icons[status]); + }, + _update_im_status_custom: function (status) { + var self = this; + this.Partners.call('write', [[session.partner_id], {'im_status_custom': status}]) + .then(function () { + self._update_systray_status_icon(status); + }); + }, + + }); + + SystrayMenu.Items.push(Systray); + +}); diff --git a/bus_presence_override/static/src/less/systray.less b/bus_presence_override/static/src/less/systray.less new file mode 100644 index 00000000..67e15406 --- /dev/null +++ b/bus_presence_override/static/src/less/systray.less @@ -0,0 +1,47 @@ +/* Copyright 2017 LasLabs Inc. + License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ + +#userStatus { + &:hover i { + color: #d3d3d3 !important; + } + + i { + padding-top: 2px; + } +} + +.o_user_presence_dropdown { + min-width: 85px; + + .o_user_presence_status { + margin-top: 2px; + padding: 4px 8px; + width: 100%; + display: block; + font-size: 13px; + } + + &:hover { + cursor: pointer; + } + +} + +@media (max-width: @screen-xs-max) { + + .o_user_presence_status { + color: #9d9d9d; + &:hover { + color: #ffffff; + } + } + } + +@media (min-width: @screen-sm-min) { + + .o_user_presence_status:hover { + background-color: #e0e0e0; + } + +} diff --git a/bus_presence_override/static/src/xml/systray.xml b/bus_presence_override/static/src/xml/systray.xml new file mode 100644 index 00000000..20383a6e --- /dev/null +++ b/bus_presence_override/static/src/xml/systray.xml @@ -0,0 +1,35 @@ + + + + + + +
  • + + +
  • +
    + +
    diff --git a/bus_presence_override/status_constants.py b/bus_presence_override/status_constants.py new file mode 100644 index 00000000..a8002ca5 --- /dev/null +++ b/bus_presence_override/status_constants.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +ONLINE = 'online' +AWAY = 'away' +OFFLINE = 'offline' diff --git a/bus_presence_override/tests/__init__.py b/bus_presence_override/tests/__init__.py new file mode 100644 index 00000000..f7eae6c5 --- /dev/null +++ b/bus_presence_override/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import test_res_partner diff --git a/bus_presence_override/tests/test_res_partner.py b/bus_presence_override/tests/test_res_partner.py new file mode 100644 index 00000000..7077d705 --- /dev/null +++ b/bus_presence_override/tests/test_res_partner.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from mock import patch + +from odoo.tests.common import TransactionCase + +from ..status_constants import ONLINE, AWAY, OFFLINE + + +GET_PRESENCE = 'odoo.addons.bus_presence_override.models.res_partner.' \ + 'ResPartner._get_partners_presence' + + +class TestResPartner(TransactionCase): + + def setUp(self): + super(TestResPartner, self).setUp() + self.admin = self.env.ref( + 'base.partner_root', + ) + + @patch(GET_PRESENCE) + def test_compute_im_status_online(self, get_presence): + """ im_status_custom and im_status should both be online """ + get_presence.return_value = {self.admin.id: ONLINE} + self.admin.im_status_custom = ONLINE + self.assertEquals( + ONLINE, + self.admin.im_status, + ) + self.assertEquals( + ONLINE, + self.admin.im_status_custom, + ) + + @patch(GET_PRESENCE) + def test_compute_im_status_custom_away_override(self, get_presence): + """ im_status_custom away should override im_status """ + get_presence.return_value = {self.admin.id: ONLINE} + self.admin.im_status_custom = AWAY + self.assertEquals( + AWAY, + self.admin.im_status, + ) + self.assertEquals( + AWAY, + self.admin.im_status_custom, + ) + + @patch(GET_PRESENCE) + def test_compute_im_status_custom_offline_override(self, get_presence): + """ im_status_custom offline should override im_status """ + get_presence.return_value = {self.admin.id: ONLINE} + self.admin.im_status_custom = OFFLINE + self.assertEquals( + OFFLINE, + self.admin.im_status, + ) + self.assertEquals( + OFFLINE, + self.admin.im_status_custom, + ) + + @patch(GET_PRESENCE) + def test_compute_im_status_away_override(self, get_presence): + """ im_status away should override im_status_custom """ + get_presence.return_value = {self.admin.id: AWAY} + self.admin.im_status_custom = ONLINE + self.assertEquals( + AWAY, + self.admin.im_status, + ) + self.assertEquals( + AWAY, + self.admin.im_status_custom, + ) + + @patch(GET_PRESENCE) + def test_compute_im_status_offline_override(self, get_presence): + """ im_status offline should override im_status_custom """ + get_presence.return_value = {self.admin.id: OFFLINE} + self.admin.im_status_custom = ONLINE + self.assertEquals( + OFFLINE, + self.admin.im_status, + ) + self.assertEquals( + OFFLINE, + self.admin.im_status_custom, + ) diff --git a/bus_presence_override/views/assets.xml b/bus_presence_override/views/assets.xml new file mode 100644 index 00000000..54b45da0 --- /dev/null +++ b/bus_presence_override/views/assets.xml @@ -0,0 +1,16 @@ + + + + + +