summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarad Mahendra <mba@openerp.com>2018-03-30 17:38:20 +0530
committerThibault Delavallée <tde@odoo.com>2018-05-31 16:49:56 +0200
commit4a59f93d157638d75647cf26d91a4237e2e7b042 (patch)
tree803fbd36e098e4d724ceedd29d750667eeca4fec
parent642970494e6f78741a8e0b792031957e4910e8ba (diff)
[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.
-rw-r--r--addons/calendar/models/__init__.py1
-rw-r--r--addons/calendar/models/res_users.py43
-rw-r--r--addons/calendar/static/src/js/systray.js79
-rw-r--r--addons/calendar/static/src/scss/calendar.scss13
-rw-r--r--addons/calendar/static/src/xml/base_calendar.xml25
-rw-r--r--addons/calendar/static/tests/systray_tests.js77
-rw-r--r--addons/calendar/views/calendar_templates.xml2
-rw-r--r--addons/calendar/views/calendar_views.xml36
-rw-r--r--addons/mail/models/res_users.py1
-rw-r--r--addons/mail/static/src/js/systray.js5
-rw-r--r--addons/mail/static/src/scss/systray.scss3
-rw-r--r--addons/mail/static/src/xml/systray.xml2
-rw-r--r--addons/mail/static/tests/systray_tests.js4
-rw-r--r--addons/note/models/res_users.py1
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_view.js2
15 files changed, 288 insertions, 6 deletions
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 @@
</t>
</t>
+ <t t-extend="mail.chat.ActivityMenuPreview">
+ <t t-jquery="div.o_channel_title" t-operation="after">
+ <div t-if="activity and activity.type == 'meeting'">
+ <t t-set="is_next_meeting" t-value="true"/>
+ <t t-foreach="activity.meetings" t-as="meeting">
+ <div>
+ <span t-att-class="!meeting.allday and is_next_meeting ? 'o_meeting_filter o_meeting_bold' : 'o_meeting_filter'" t-att-data-res_model="activity.model" t-att-data-res_id="meeting.id" t-att-data-model_name="activity.name" t-att-title="meeting.name">
+ <span t-if="!meeting.allday">
+ <t t-esc="moment(meeting.start).local().format('hh:mm A')"/>
+ </span>
+ <span><t t-esc="meeting.name"/></span>
+ </span>
+ <span t-if="meeting.start" class="pull-right">
+ <t t-if="meeting.allday">All Day</t>
+ <t t-else=''>
+ <t t-set="is_next_meeting" t-value="false"/>
+ <t t-esc="moment(meeting.start).fromNow()"/>
+ </t>
+ </span>
+ </div>
+ </t>
+ </div>
+ </t>
+ </t>
+
</template>
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 @@
<link rel="stylesheet" type="text/scss" href="/calendar/static/src/scss/calendar.scss"/>
<script type="text/javascript" src="/calendar/static/src/js/base_calendar.js"></script>
<script type="text/javascript" src="/calendar/static/src/js/mail_activity.js"></script>
+ <script type="text/javascript" src="/calendar/static/src/js/systray.js"></script>
</xpath>
</template>
<template id="qunit_suite" inherit_id="web.qunit_suite">
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/calendar/static/tests/calendar_tests.js"/>
+ <script type="text/javascript" src="/calendar/static/tests/systray_tests.js"/>
</xpath>
</template>
diff --git a/addons/calendar/views/calendar_views.xml b/addons/calendar/views/calendar_views.xml
index 80994d946fa..cb17b894f79 100644
--- a/addons/calendar/views/calendar_views.xml
+++ b/addons/calendar/views/calendar_views.xml
@@ -321,6 +321,42 @@
</field>
</record>
+<!-- <record id="view_calendar_today_event_calendar" model="ir.ui.view">
+ <field name="name">calendar.event.calendar</field>
+ <field name="model">calendar.event</field>
+ <field name="priority" eval="2"/>
+ <field name="arch" type="xml">
+ <calendar string="Meetings" date_start="start" date_stop="stop" date_delay="duration" all_day="allday"
+ readonly_form_view_id="%(calendar.view_calendar_event_form_popup)s"
+ event_open_popup="true"
+ event_limit="3"
+ color="partner_id"
+ mode="day">
+ <field name="name"/>
+ <field name="partner_ids" write_model="calendar.contacts" write_field="partner_id" avatar_field="image_small"/>
+ <field name="is_highlighted" invisible="1"/>
+ </calendar>
+ </field>
+ </record> -->
+
+<!-- <record id="action_today_calendar_event" model="ir.actions.act_window">
+ <field name="name">Today's Meetings</field>
+ <field name="res_model">calendar.event</field>
+ <field name="view_mode">calendar,tree,form</field>
+ <field name="view_id" ref="view_calendar_today_event_calendar"/>
+ <field name="search_view_id" ref="view_calendar_event_search"/>
+ <field name="context">{'search_default_mymeetings': 1}</field>
+ <field name="help" type="html">
+ <p class="o_view_nocontent_smiling_face">
+ Schedule a new meeting
+ </p><p>
+ The calendar is shared between employees and fully integrated with
+ other applications such as the employee leaves or the business
+ opportunities.
+ </p>
+ </field>
+ </record> -->
+
<record id="action_view_calendar_event_calendar" model="ir.actions.act_window.view">
<field name="act_window_id" ref="action_calendar_event"/>
<field name="sequence" eval="1"/>
diff --git a/addons/mail/models/res_users.py b/addons/mail/models/res_users.py
index af2cea3c0fe..f9ebd21eb1b 100644
--- a/addons/mail/models/res_users.py
+++ b/addons/mail/models/res_users.py
@@ -98,6 +98,7 @@ class Users(models.Model):
user_activities[activity['model']] = {
'name': model_names[activity['id']],
'model': activity['model'],
+ 'type': 'activity',
'icon': modules.module.get_module_icon(self.env[activity['model']]._original_module),
'total_count': 0, 'today_count': 0, 'overdue_count': 0, 'planned_count': 0,
}
diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js
index e5f8c870562..a22bbed3a60 100644
--- a/addons/mail/static/src/js/systray.js
+++ b/addons/mail/static/src/js/systray.js
@@ -180,9 +180,8 @@ var ActivityMenu = Widget.extend({
return self._rpc({
model: 'res.users',
method: 'systray_get_activities',
- kwargs: {
- context: session.user_context,
- },
+ args: [],
+ kwargs: {context: session.user_context},
}).then(function (data) {
self.activities = data;
self.activityCounter = _.reduce(data, function (total_count, p_data) { return total_count + p_data.total_count; }, 0);
diff --git a/addons/mail/static/src/scss/systray.scss b/addons/mail/static/src/scss/systray.scss
index fd799cb9cc7..b0ed61e140f 100644
--- a/addons/mail/static/src/scss/systray.scss
+++ b/addons/mail/static/src/scss/systray.scss
@@ -67,10 +67,11 @@
@media (min-width: $screen-sm-min) {
.o_mail_channel_preview {
- height: 50px;
+ min-height: 50px;
padding: 5px;
.o_mail_channel_image {
width: 40px;
+ height: 40px;
}
.o_channel_info {
margin-left: 10px;
diff --git a/addons/mail/static/src/xml/systray.xml b/addons/mail/static/src/xml/systray.xml
index 54df73c1b94..a6e52ab555a 100644
--- a/addons/mail/static/src/xml/systray.xml
+++ b/addons/mail/static/src/xml/systray.xml
@@ -69,7 +69,7 @@
<t t-esc="activity.name"/>
</span>
</div>
- <div>
+ <div t-if="activity and activity.type == 'activity'">
<button t-if="activity.overdue_count" type="button" class="btn btn-link o_activity_filter_button mr16" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" data-filter='overdue'><t t-esc="activity.overdue_count"/> Late </button>
<span t-if="!activity.overdue_count" class="o_no_activity mr16">0 Late </span>
<button t-if="activity.today_count" type="button" class="btn btn-link o_activity_filter_button mr16" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" data-filter='today'> <t t-esc="activity.today_count"/> Today </button>
diff --git a/addons/mail/static/tests/systray_tests.js b/addons/mail/static/tests/systray_tests.js
index c27a2978fb0..b30b5e7b279 100644
--- a/addons/mail/static/tests/systray_tests.js
+++ b/addons/mail/static/tests/systray_tests.js
@@ -20,6 +20,7 @@ QUnit.module('ActivityMenu', {
fields: {
name: { type: "char" },
model: { type: "char" },
+ type: { type: "char" },
planned_count: { type: "integer"},
today_count: { type: "integer"},
overdue_count: { type: "integer"},
@@ -28,6 +29,7 @@ QUnit.module('ActivityMenu', {
records: [{
name: "Contact",
model: "res.partner",
+ type: "activity",
planned_count: 0,
today_count: 1,
overdue_count: 0,
@@ -35,6 +37,7 @@ QUnit.module('ActivityMenu', {
},
{
name: "Task",
+ type: "activity",
model: "project.task",
planned_count: 1,
today_count: 0,
@@ -43,6 +46,7 @@ QUnit.module('ActivityMenu', {
},
{
name: "Issue",
+ type: "activity",
model: "project.issue",
planned_count: 1,
today_count: 1,
diff --git a/addons/note/models/res_users.py b/addons/note/models/res_users.py
index 6f248c47664..d1bca02e65c 100644
--- a/addons/note/models/res_users.py
+++ b/addons/note/models/res_users.py
@@ -63,6 +63,7 @@ GROUP BY id"""
activities[note_index]['name'] = note_label
else:
activities.append({
+ 'type': 'activity',
'name': note_label,
'model': 'note.note',
'icon': modules.module.get_module_icon(self.env['note.note']._original_module),
diff --git a/addons/web/static/src/js/views/calendar/calendar_view.js b/addons/web/static/src/js/views/calendar/calendar_view.js
index 6c12d508937..c770c9eb642 100644
--- a/addons/web/static/src/js/views/calendar/calendar_view.js
+++ b/addons/web/static/src/js/views/calendar/calendar_view.js
@@ -152,7 +152,7 @@ var CalendarView = AbstractView.extend({
this.loadParams.fieldColor = attrs.color;
this.loadParams.filters = filters;
- this.loadParams.mode = attrs.mode;
+ this.loadParams.mode = (params.context && params.context.default_mode) || attrs.mode;
this.loadParams.initialDate = moment(params.initialDate || new Date());
},
});