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 @@ +