From 4a59f93d157638d75647cf26d91a4237e2e7b042 Mon Sep 17 00:00:00 2001 From: Barad Mahendra Date: Fri, 30 Mar 2018 17:38:20 +0530 Subject: [PATCH] [IMP] mail, calendar: add today meetings in systray Purpose of this commit is to display today meetings in the systray. This will help people in their daily job to have quickly access to their meetings. It is displayed along with activities and reminders to have all current work to do in the same place. Displayed meetings are ongoing or not yet started ones of the current day. All-day meetings of the current day are also shown. Done meetings are not displayed to lessen noise. Clicking on the systray will open the calendar view of meetings in a day mode. This is done by a small modification in the calendar view allowing to choose the display mode from context in addition to the mode attribute of the action definition. This commit is linked to task ID 60593. Closes #24438. Thanks to @ged-odoo for quick reviewing. --- addons/calendar/models/__init__.py | 1 + addons/calendar/models/res_users.py | 43 ++++++++++ addons/calendar/static/src/js/systray.js | 79 +++++++++++++++++++ addons/calendar/static/src/scss/calendar.scss | 13 +++ .../calendar/static/src/xml/base_calendar.xml | 25 ++++++ addons/calendar/static/tests/systray_tests.js | 77 ++++++++++++++++++ addons/calendar/views/calendar_templates.xml | 2 + addons/calendar/views/calendar_views.xml | 36 +++++++++ addons/mail/models/res_users.py | 1 + addons/mail/static/src/js/systray.js | 5 +- addons/mail/static/src/scss/systray.scss | 3 +- addons/mail/static/src/xml/systray.xml | 2 +- addons/mail/static/tests/systray_tests.js | 4 + addons/note/models/res_users.py | 1 + .../src/js/views/calendar/calendar_view.js | 2 +- 15 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 addons/calendar/models/res_users.py create mode 100644 addons/calendar/static/src/js/systray.js create mode 100644 addons/calendar/static/tests/systray_tests.js diff --git a/addons/calendar/models/__init__.py b/addons/calendar/models/__init__.py index 06a98e823e5..e83190ac8a0 100644 --- a/addons/calendar/models/__init__.py +++ b/addons/calendar/models/__init__.py @@ -7,3 +7,4 @@ from . import res_partner from . import mail_message from . import calendar from . import mail_activity +from . import res_users diff --git a/addons/calendar/models/res_users.py b/addons/calendar/models/res_users.py new file mode 100644 index 00000000000..4d8cc0dd8e7 --- /dev/null +++ b/addons/calendar/models/res_users.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import datetime + +from odoo import api, fields, models, modules, _ + + +class Users(models.Model): + _inherit = 'res.users' + + def _systray_get_calendar_event_domain(self): + start_dt = datetime.datetime.utcnow() + start_date = datetime.date.today() + end_dt = datetime.datetime.combine(start_date, datetime.time.max) + + return ['&', '|', + '&', + '|', ['start', '>=', fields.Datetime.to_string(start_dt)], ['stop', '>=', fields.Datetime.to_string(start_dt)], + ['start', '<=', fields.Datetime.to_string(end_dt)], + '&', ['allday', '=', True], ['start_date', '=', fields.Date.to_string(start_date)], + '&', ('attendee_ids.partner_id', '=', self.env.user.partner_id.id), ('attendee_ids.state', '!=', 'declined')] + + @api.model + def systray_get_activities(self): + res = super(Users, self).systray_get_activities() + + meetings_lines = self.env['calendar.event'].search_read( + self._systray_get_calendar_event_domain(), + ['id', 'start', 'name', 'allday'], + order='start') + if meetings_lines: + meeting_label = _("Today's Meetings") + meetings_systray = { + 'type': 'meeting', + 'name': meeting_label, + 'model': 'calendar.event', + 'icon': modules.module.get_module_icon(self.env['calendar.event']._original_module), + 'meetings': meetings_lines, + } + res.insert(0, meetings_systray) + + return res diff --git a/addons/calendar/static/src/js/systray.js b/addons/calendar/static/src/js/systray.js new file mode 100644 index 00000000000..f0ad7b35304 --- /dev/null +++ b/addons/calendar/static/src/js/systray.js @@ -0,0 +1,79 @@ +odoo.define('calendar.systray', function (require) { +"use strict"; + +var ActivityMenu = require('mail.systray').ActivityMenu; +var fieldUtils = require('web.field_utils'); + +ActivityMenu.include({ + events: _.extend({}, ActivityMenu.prototype.events, { + 'click .o_meeting_filter': '_onMeetingFilterClick' + }), + + //----------------------------------------- + // Private + //----------------------------------------- + + /** + * parse date to server value + * + * @private + * @override + */ + _getActivityData: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + var meeting = _.find(self.activities, {type: 'meeting'}); + if (meeting && meeting.meetings) { + _.each(meeting.meetings, function (res) { + res.start = fieldUtils.parse.datetime(res.start, false, {isUTC: true}); + }); + } + }); + }, + + //----------------------------------------- + // Handlers + //----------------------------------------- + + /** + * @private + * @override + */ + _onActivityFilterClick: function (ev) { + var $el = $(ev.currentTarget); + var data = _.extend({}, $el.data()); + if (data.res_model === "calendar.event" && data.filter === "my") { + this.do_action('calendar.action_calendar_event', { + additional_context: { + default_mode: 'day', + search_default_mymeetings: 1, + } + }); + } else { + this._super.apply(this, arguments); + } + }, + + /** + * When particular meeting clicked, open particular meeting in form view + * + * @private + * @param {MouseEvent} ev + */ + _onMeetingFilterClick: function (ev) { + ev.stopPropagation(); + var $el = $(ev.currentTarget); + var data = _.extend({}, $el.data()); + if (data.res_model === "calendar.event") { + this.do_action({ + type: 'ir.actions.act_window', + name: data.model_name, + res_model: data.res_model, + res_id: data.res_id, + views: [[false, 'form'], [false, 'calendar'], [false, 'list']], + }); + } + }, +}); + +}); diff --git a/addons/calendar/static/src/scss/calendar.scss b/addons/calendar/static/src/scss/calendar.scss index a74eec477db..6d75477cfb6 100644 --- a/addons/calendar/static/src/scss/calendar.scss +++ b/addons/calendar/static/src/scss/calendar.scss @@ -77,3 +77,16 @@ color: $brand-danger; } } +.o_meeting_filter { + @include text-overflow(); + width: 64%; + color: grey; + display: inline-block; + vertical-align: top; + &:hover { + color: $o-brand-optional; + } + &.o_meeting_bold { + font-weight: bold; + } +} diff --git a/addons/calendar/static/src/xml/base_calendar.xml b/addons/calendar/static/src/xml/base_calendar.xml index ac6bfcd2945..56d10a45afe 100644 --- a/addons/calendar/static/src/xml/base_calendar.xml +++ b/addons/calendar/static/src/xml/base_calendar.xml @@ -20,4 +20,29 @@ + + +
+ + +
+ + + + + + + + All Day + + + + + +
+
+
+
+
+ diff --git a/addons/calendar/static/tests/systray_tests.js b/addons/calendar/static/tests/systray_tests.js new file mode 100644 index 00000000000..072a1b5f4f1 --- /dev/null +++ b/addons/calendar/static/tests/systray_tests.js @@ -0,0 +1,77 @@ +odoo.define('calendar.systray_tests', function (require) { +"use strict"; + +var ChatManager = require('mail.ChatManager'); +var systray = require('mail.systray'); +var testUtils = require('web.test_utils'); +var createBusService = require('mail.testUtils').createBusService; + + +QUnit.module('calendar', {}, function () { + +QUnit.module('ActivityMenu', { + beforeEach: function () { + this.services = [ChatManager, createBusService()]; + this.data = { + 'calendar.event': { + records: [{ + name: "Today's meeting (3)", + model: "calendar.event", + type: 'meeting', + meetings: [{ + id: 1, + res_model: "calendar.event", + name: "meeting1", + start: "2018-04-20 06:30:00", + allday: false, + },{ + id: 2, + res_model: "calendar.event", + name: "meeting2", + start: "2018-04-20 09:30:00", + allday: false, + }] + }], + }, + }; + } +}); + +QUnit.test('activity menu widget:today meetings', function (assert) { + assert.expect(8); + var self = this; + var activityMenu = new systray.ActivityMenu(); + testUtils.addMockEnvironment(activityMenu, { + services: this.services, + mockRPC: function (route, args) { + if (args.method === 'systray_get_activities') { + return $.when(self.data['calendar.event']['records']); + } + return this._super(route, args); + }, + }); + + activityMenu.appendTo($('#qunit-fixture')); + + assert.ok(activityMenu.$el.hasClass('o_mail_navbar_item'), 'should be the instance of widget'); + + testUtils.intercept(activityMenu, 'do_action', function (event) { + assert.ok(true, "should have triggered a do_action"); + assert.strictEqual(event.data.action.res_id, activityMenu.$('.o_meeting_filter').eq(0).data('res_id'), 'should open particular meeting form view') + }); + activityMenu.$('.dropdown-toggle').click(); + activityMenu.$('.o_meeting_filter').eq(0).click(); + + testUtils.intercept(activityMenu, 'do_action', function (event) { + assert.strictEqual(event.data.action, "calendar.action_calendar_event", 'should open meeting calendar view in day mode') + }); + activityMenu.$('.o_mail_channel_preview').click(); + + assert.ok(activityMenu.$('.o_meeting_filter'), "should be a meeting"); + assert.strictEqual(activityMenu.$('.o_meeting_filter').length, 2, 'there should be 2 meetings'); + assert.strictEqual(activityMenu.$('.o_meeting_filter').eq(0).hasClass('o_meeting_bold'), true, 'this meeting is yet to start'); + assert.strictEqual(activityMenu.$('.o_meeting_filter').eq(1).hasClass('o_meeting_bold'), false, 'this meeting has been started'); + activityMenu.destroy(); +}); +}); +}); diff --git a/addons/calendar/views/calendar_templates.xml b/addons/calendar/views/calendar_templates.xml index c963102eee2..2c9660de77e 100644 --- a/addons/calendar/views/calendar_templates.xml +++ b/addons/calendar/views/calendar_templates.xml @@ -5,12 +5,14 @@ +