diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py
index 105d149fa43..4a23ef2573c 100644
--- a/addons/mail/__manifest__.py
+++ b/addons/mail/__manifest__.py
@@ -38,6 +38,7 @@
],
'demo': [
'data/mail_demo.xml',
+ 'data/mail_activity_demo.xml',
'data/mail_channel_demo.xml',
],
'installable': True,
diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py
index f0990c7de40..9682cbe1125 100644
--- a/addons/mail/controllers/main.py
+++ b/addons/mail/controllers/main.py
@@ -230,3 +230,15 @@ class MailController(http.Controller):
'menu_id': request.env['ir.model.data'].xmlid_to_res_id('mail.mail_channel_menu_root_chat'),
}
return values
+
+ @http.route('/mail/activity/new', type='json', auth='user')
+ def mail_activity_new(self, note, activity_type_id=None, date_deadline=None):
+ values = {'note': note}
+ if date_deadline:
+ values['date_deadline'] = date_deadline
+ if not activity_type_id:
+ activity_type_id = request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')]).id
+ if activity_type_id:
+ values['activity_type_id'] = activity_type_id
+ activity = request.env['mail.activity'].create(values)
+ return activity.id
diff --git a/addons/mail/data/mail_activity_data.xml b/addons/mail/data/mail_activity_data.xml
index 281d1915141..79f01eeaf02 100644
--- a/addons/mail/data/mail_activity_data.xml
+++ b/addons/mail/data/mail_activity_data.xml
@@ -19,5 +19,11 @@
fa-tasks
5
+
+ Reminder
+ reminder
+ fa-tasks
+ 0
+
diff --git a/addons/mail/data/mail_activity_demo.xml b/addons/mail/data/mail_activity_demo.xml
new file mode 100644
index 00000000000..f15a84331d4
--- /dev/null
+++ b/addons/mail/data/mail_activity_demo.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ Lunch with colleagues this Sunday.
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/mail/data/mail_demo.xml b/addons/mail/data/mail_demo.xml
index 0aa8d664ab8..b4e7f2c092e 100644
--- a/addons/mail/data/mail_demo.xml
+++ b/addons/mail/data/mail_demo.xml
@@ -220,6 +220,5 @@ virginie@agrolait.fr
-
diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py
index c08866fa6a4..1259e7395f8 100644
--- a/addons/mail/models/mail_activity.py
+++ b/addons/mail/models/mail_activity.py
@@ -5,6 +5,7 @@ from datetime import date, datetime, timedelta
from odoo import api, exceptions, fields, models, _
from odoo.osv import expression
+from odoo.tools import html2plaintext
class MailActivityType(models.Model):
@@ -45,7 +46,7 @@ class MailActivityType(models.Model):
'mail.activity.type', 'mail_activity_rel', 'recommended_id', 'activity_id',
string='Preceding Activities')
category = fields.Selection([
- ('default', 'Other')], default='default',
+ ('default', 'Other'), ('reminder', 'Reminder')], default='default',
string='Category',
help='Categories may trigger specific behavior like opening calendar view')
@@ -69,10 +70,10 @@ class MailActivity(models.Model):
return res
# owner
- res_id = fields.Integer('Related Document ID', index=True, required=True)
+ res_id = fields.Integer('Related Document ID', index=True)
res_model_id = fields.Many2one(
'ir.model', 'Document Model',
- index=True, ondelete='cascade', required=True)
+ index=True, ondelete='cascade')
res_model = fields.Char(
'Related Document Model',
index=True, related='res_model_id.model', store=True, readonly=True)
@@ -89,6 +90,9 @@ class MailActivity(models.Model):
note = fields.Html('Note')
feedback = fields.Html('Feedback')
date_deadline = fields.Date('Due Date', index=True, required=True, default=fields.Date.today)
+ active = fields.Boolean(
+ 'Open', default=True,
+ help='Done reminders should be archived instead of marked as done.')
# description
user_id = fields.Many2one(
'res.users', 'Assigned to',
@@ -115,7 +119,8 @@ class MailActivity(models.Model):
@api.depends('res_model', 'res_id')
def _compute_res_name(self):
for activity in self:
- activity.res_name = self.env[activity.res_model].browse(activity.res_id).name_get()[0][1]
+ if activity.res_model and activity.res_id:
+ activity.res_name = self.env[activity.res_model].browse(activity.res_id).name_get()[0][1]
@api.depends('date_deadline')
def _compute_state(self):
@@ -162,11 +167,17 @@ class MailActivity(models.Model):
pass
else:
return
-
doc_operation = 'read' if operation == 'read' else 'write'
activity_to_documents = dict()
for activity in self.sudo():
- activity_to_documents.setdefault(activity.res_model, list()).append(activity.res_id)
+ if activity.res_model and activity.res_id:
+ activity_to_documents.setdefault(activity.res_model, list()).append(activity.res_id)
+ else:
+ if (operation != 'create' and self.env.user.id != activity.user_id.id):
+ raise exceptions.AccessError(_("You can only access your own records."))
+ elif operation == 'create' and not self.env.user.has_group('base.group_user'):
+ raise exceptions.AccessError(_("Only employee can create reminder."))
+
for model, res_ids in activity_to_documents.items():
self.env[model].check_access_rights(doc_operation, raise_exception=True)
try:
@@ -182,11 +193,19 @@ class MailActivity(models.Model):
values_w_defaults = self.default_get(self._fields.keys())
values_w_defaults.update(values)
+ # Reminder have no summary (for display name) It will add first line of note to summary.
+ if 'res_model_id' not in values and 'summary' not in values:
+ values_w_defaults['summary'] = self._compute_summary_from_note(values.get('note', _('Reminder')))
+
# continue as sudo because activities are somewhat protected
activity = super(MailActivity, self.sudo()).create(values_w_defaults)
activity_user = activity.sudo(self.env.user)
activity_user._check_access('create')
- self.env[activity_user.res_model].browse(activity_user.res_id).message_subscribe(partner_ids=[activity_user.user_id.partner_id.id])
+
+ # subscribe to document if any
+ if activity.res_id and activity.res_model:
+ self.env[activity_user.res_model].browse(activity_user.res_id).message_subscribe(partner_ids=[activity_user.user_id.partner_id.id])
+
if activity.date_deadline <= fields.Date.today():
self.env['bus.bus'].sendone(
(self._cr.dbname, 'res.partner', activity.user_id.partner_id.id),
@@ -196,13 +215,28 @@ class MailActivity(models.Model):
@api.multi
def write(self, values):
self._check_access('write')
+ if not self.env.user._is_admin() and any(field in values.keys() for field in ['res_model', 'res_id', 'res_model_id']):
+ raise exceptions.AccessError(_("You cannot re-attach a reminder to another record."))
+
if values.get('user_id'):
pre_responsibles = self.mapped('user_id.partner_id')
- res = super(MailActivity, self.sudo()).write(values)
+
+ reminders = self.filtered(lambda a: not a.res_id and not a.res_model) if 'note' in values and 'summary' not in values else self.env['mail.activity']
+ activities = self - reminders
+ res = True
+ if activities:
+ res &= super(MailActivity, activities.sudo()).write(values)
+ if reminders: # if we have reminder, note is set and summary is not set. We need to update summary
+ upd_values = dict(
+ values,
+ summary=self._compute_summary_from_note(values['note'])
+ )
+ res &= super(MailActivity, reminders.sudo()).write(upd_values)
if values.get('user_id'):
for activity in self:
- self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[activity.user_id.partner_id.id])
+ if activity.res_id and activity.res_model: # subscribe to document if any
+ self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[activity.user_id.partner_id.id])
if activity.date_deadline <= fields.Date.today():
self.env['bus.bus'].sendone(
(self._cr.dbname, 'res.partner', activity.user_id.partner_id.id),
@@ -225,6 +259,29 @@ class MailActivity(models.Model):
{'type': 'activity_updated', 'activity_deleted': True})
return super(MailActivity, self.sudo()).unlink()
+ @api.multi
+ def toggle_active(self):
+ """ Override model-method to send bus notification about (un)archived reminders """
+ res = super(MailActivity, self).toggle_active()
+ for activity in self:
+ if activity.active:
+ self.env['bus.bus'].sendone(
+ (self._cr.dbname, 'res.partner', activity.user_id.partner_id.id),
+ {'type': 'activity_updated', 'activity_created': True})
+ else:
+ self.env['bus.bus'].sendone(
+ (self._cr.dbname, 'res.partner', activity.user_id.partner_id.id),
+ {'type': 'activity_updated', 'activity_deleted': True})
+ return res
+
+ def _compute_summary_from_note(self, note):
+ """ Returns the first line of html note """
+ if note:
+ summary = html2plaintext(note).strip().replace('*', '').split("\n")[0]
+ else:
+ summary = _('Reminder')
+ return summary or _('Reminder')
+
@api.multi
def action_done(self):
""" Wrapper without feedback because web button add context as
diff --git a/addons/mail/models/res_users.py b/addons/mail/models/res_users.py
index cc6786cce1e..5b7c82d6725 100644
--- a/addons/mail/models/res_users.py
+++ b/addons/mail/models/res_users.py
@@ -70,15 +70,16 @@ class Users(models.Model):
@api.model
def activity_user_count(self):
- query = """SELECT m.id, count(*), act.res_model as model,
+ # Reminders don't having any model so here we assign 'mail.activity' as reminder's model.
+ query = """SELECT m.id, count(*), act.res_model AS model,
CASE
WHEN now()::date - act.date_deadline::date = 0 Then 'today'
WHEN now()::date - act.date_deadline::date > 0 Then 'overdue'
WHEN now()::date - act.date_deadline::date < 0 Then 'planned'
END AS states
FROM mail_activity AS act
- JOIN ir_model AS m ON act.res_model_id = m.id
- WHERE user_id = %s
+ LEFT OUTER JOIN ir_model AS m ON act.res_model_id = m.id
+ WHERE user_id = %s AND active = TRUE
GROUP BY m.id, states, act.res_model;
"""
self.env.cr.execute(query, [self.env.uid])
@@ -86,7 +87,18 @@ class Users(models.Model):
model_ids = [a['id'] for a in activity_data]
model_names = {n[0]:n[1] for n in self.env['ir.model'].browse(model_ids).name_get()}
- user_activities = {}
+ # always add default entry for reminders
+ user_activities = {
+ None: {
+ 'name': _("Reminder"),
+ 'model': None,
+ 'icon': '/mail/static/src/img/reminder.png',
+ 'total_count': 0,
+ 'today_count': 0,
+ 'overdue_count': 0,
+ 'planned_count': 0
+ }
+ }
for activity in activity_data:
if not user_activities.get(activity['model']):
user_activities[activity['model']] = {
@@ -96,7 +108,7 @@ class Users(models.Model):
'total_count': 0, 'today_count': 0, 'overdue_count': 0, 'planned_count': 0,
}
user_activities[activity['model']]['%s_count' % activity['states']] += activity['count']
- if activity['states'] in ('today','overdue'):
+ if activity['states'] in ('today', 'overdue'):
user_activities[activity['model']]['total_count'] += activity['count']
return list(user_activities.values())
diff --git a/addons/mail/static/src/img/reminder.png b/addons/mail/static/src/img/reminder.png
new file mode 100644
index 00000000000..a585b8d6e09
Binary files /dev/null and b/addons/mail/static/src/img/reminder.png differ
diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js
index f6c19a1c164..d382745df29 100644
--- a/addons/mail/static/src/js/systray.js
+++ b/addons/mail/static/src/js/systray.js
@@ -3,11 +3,13 @@ odoo.define('mail.systray', function (require) {
var config = require('web.config');
var core = require('web.core');
+var datepicker = require('web.datepicker');
var session = require('web.session');
var SystrayMenu = require('web.SystrayMenu');
var Widget = require('web.Widget');
var QWeb = core.qweb;
+var _t = core._t;
/**
* Menu item appended in the systray part of the navbar
@@ -157,8 +159,13 @@ var MessagingMenu = Widget.extend({
var ActivityMenu = Widget.extend({
template:'mail.chat.ActivityMenu',
events: {
- "click": "_onActivityMenuClick",
- "click .o_mail_channel_preview": "_onActivityFilterClick",
+ 'click': '_onActivityMenuClick',
+ 'click .o_mail_channel_preview:not(.o_reminder)': '_onActivityFilterClick',
+ 'click .o_reminder_show': '_onAddReminderClick',
+ 'click .o_reminder_save': '_onReminderSaveClick',
+ 'click .o_reminder_set_datetime': '_onReminderDateTimeSetClick',
+ 'keydown input.o_reminder_input': '_onReminderInputKeyDown',
+ 'click .o_reminder': '_onNewReminderClick',
},
start: function () {
this.$activities_preview = this.$('.o_mail_navbar_dropdown_channels');
@@ -167,9 +174,9 @@ var ActivityMenu = Widget.extend({
this._updateActivityPreview();
return this._super();
},
-
+ //--------------------------------------------------
// Private
-
+ //--------------------------------------------------
/**
* Make RPC and get current user's activity details
* @private
@@ -184,8 +191,21 @@ var ActivityMenu = Widget.extend({
context: session.user_context,
},
}).then(function (data) {
+ // We required reminder to be first always. Moving its position from n-th to zero if not at zero
+ var reminderIndex = _.findIndex(data, function (val) { return val.model == null; });
+ if (reminderIndex > 0) {
+ data.splice(0, 0, data.splice(reminderIndex, 1)[0]);
+ }
self.activities = data;
- self.activityCounter = _.reduce(data, function (total_count, p_data) { return total_count + p_data.total_count; }, 0);
+
+ //we want to add count in reminders title
+ var reminders = _.findWhere(data, {'model': null});
+ if (reminders) {
+ var totalReminders = reminders.overdue_count + reminders.planned_count + reminders.today_count;
+ //manualy count total reminders since reminders.total_count only sum today and overdue
+ reminders.name = _.str.sprintf(_t('Reminders (%s)'), totalReminders);
+ }
+ self.activityCounter = _.reduce(self.activities, function (total_count, p_data) { return total_count + p_data.total_count; }, 0);
self.$('.o_notification_counter').text(self.activityCounter);
self.$el.toggleClass('o_no_notification', !self.activityCounter);
});
@@ -241,10 +261,30 @@ var ActivityMenu = Widget.extend({
this.$el.toggleClass('o_no_notification', !this.activityCounter);
}
},
-
-
+ /**
+ * Save the reminder to database using datepicker date and field as note
+ * @private
+ */
+ _saveReminder: function () {
+ var note = this.$('.o_reminder_input').val().trim();
+ if (! note) {
+ return;
+ }
+ var params = {'note': note};
+ var reminderDateTime = this.reminderDateTimeWidget.getValue();
+ if (reminderDateTime) {
+ params = _.extend(params, {'date_deadline': reminderDateTime});
+ }
+ this.$('.o_reminder_show').removeClass('hidden');
+ this.$('.o_reminder').addClass('hidden');
+ this._rpc({
+ route: '/mail/activity/new',
+ params: params,
+ }).then(this._updateActivityPreview.bind(this));
+ },
+ //------------------------------------------------------------
// Handlers
-
+ //-----------------------------------------------------------
/**
* Redirect to particular model view
* @private
@@ -260,15 +300,20 @@ var ActivityMenu = Widget.extend({
} else {
context['search_default_activities_' + data.filter] = 1;
}
- this.do_action({
- type: 'ir.actions.act_window',
- name: data.model_name,
- res_model: data.res_model,
- views: [[false, 'kanban'], [false, 'form']],
- search_view_id: [false],
- domain: [['activity_user_id', '=', session.uid]],
- context:context,
- });
+ if (data.res_model == null) {
+ // Opening Reminder custom kanban view with action.
+ this.do_action('mail.mail_activity_action_reminders', {additional_context: context});
+ } else {
+ this.do_action({
+ type: 'ir.actions.act_window',
+ name: data.model_name,
+ res_model: data.res_model,
+ views: [[false, 'kanban'], [false, 'form']],
+ search_view_id: [false],
+ domain: [['activity_user_id', '=', session.uid]],
+ context:context,
+ });
+ }
},
/**
* When menu clicked update activity preview if counter updated
@@ -280,7 +325,65 @@ var ActivityMenu = Widget.extend({
this._updateActivityPreview();
}
},
-
+ /**
+ * When add new reminder button clicked, toggling quick reminder create view inside
+ * Systray activity view
+ *
+ * @private
+ * @param {MouseEvent} event
+ */
+ _onAddReminderClick: function (event) {
+ event.stopPropagation();
+ if (!this.reminderDateTimeWidget){
+ this.reminderDateTimeWidget = new datepicker.DateWidget(this, {useCurrent: true});
+ }
+ this.reminderDateTimeWidget.appendTo(this.$('.o_reminder_datetime'));
+ this.$('.o_reminder_show, .o_reminder').toggleClass('hidden');
+ this.$('.o_reminder_input').val('').focus();
+
+ },
+ /**
+ * When focusing on input for new quick reminder systerm tray must be open.
+ * Preventing to close
+ *
+ * @private
+ * @param {MouseEvent} event
+ */
+ _onNewReminderClick: function (event) {
+ event.stopPropagation();
+ },
+ /**
+ * Opens datetime picker for reminder.
+ * Quick FIX due to no option for set custom icon instead of caret in datepicker.
+ *
+ * @private
+ * @param {MouseEvent} event
+ */
+ _onReminderDateTimeSetClick: function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.reminderDateTimeWidget.$input.click();
+ },
+ /**
+ * Saving reminder (quick create) and updating activity preview
+ *
+ * @private
+ * @param {MouseEvent} event
+ */
+ _onReminderSaveClick: function (event) {
+ this._saveReminder();
+ },
+ /**
+ * Handling Enter key for quick create reminder.
+ *
+ * @private
+ * @param {KeyboardEvent} event
+ */
+ _onReminderInputKeyDown: function (event) {
+ if (event.which === $.ui.keyCode.ENTER) {
+ this._saveReminder();
+ }
+ },
});
SystrayMenu.Items.push(MessagingMenu);
diff --git a/addons/mail/static/src/less/discuss.less b/addons/mail/static/src/less/discuss.less
index 4e0ae26dccc..b2bfc6236c8 100644
--- a/addons/mail/static/src/less/discuss.less
+++ b/addons/mail/static/src/less/discuss.less
@@ -391,7 +391,7 @@
.o_channel_title {
.o-flex-display();
.o_channel_name {
- .o-flex(0, 1, auto);
+ .o-flex(1, 1, auto);
.o-text-overflow();
}
.o_channel_counter {
@@ -428,6 +428,27 @@
}
}
+.o_reminder.o_mail_channel_preview {
+ background-color: white;
+ .o_reminder_input {
+ border: none;
+ }
+ .o_reminder_datetime {
+ .o_datepicker {
+ .o_datepicker_input {
+ float: right;
+ text-align: right;
+ border: none;
+ font-size: 11px;
+ width: 50%;
+ }
+ .o_datepicker_button {
+ display: none;
+ }
+ }
+ }
+}
+
@media (max-width: @screen-xs-max) {
@o-mail-chatter-mobile-gap: 2%;
diff --git a/addons/mail/static/src/less/mail_activity.less b/addons/mail/static/src/less/mail_activity.less
index c9fd7c8595c..f98e0974b7b 100644
--- a/addons/mail/static/src/less/mail_activity.less
+++ b/addons/mail/static/src/less/mail_activity.less
@@ -132,7 +132,15 @@
.o_kanban_inline_block {
display: inline-block;
}
-
+ .o_text_line_through {
+ text-decoration: line-through;
+ }
+ .o_reminder_title {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ padding-bottom: 10px;
+ }
.o_mail_activity {
.o_activity_btn {
span.fa {
diff --git a/addons/mail/static/src/xml/systray.xml b/addons/mail/static/src/xml/systray.xml
index dd61c486b9b..e70d36ed4c1 100644
--- a/addons/mail/static/src/xml/systray.xml
+++ b/addons/mail/static/src/xml/systray.xml
@@ -80,6 +80,29 @@
+
+
+
+
+
+
+
+
Set a reminder
+
+
+
+
+
+
+
+ SAVE
+
+
+
+
+
diff --git a/addons/mail/static/tests/systray_tests.js b/addons/mail/static/tests/systray_tests.js
index 4ac60c7e123..7ad26493cb2 100644
--- a/addons/mail/static/tests/systray_tests.js
+++ b/addons/mail/static/tests/systray_tests.js
@@ -18,15 +18,16 @@ QUnit.module('ActivityMenu', {
'mail.activity.menu': {
fields: {
name: { type: "char" },
- res_model: { type: "char" },
+ model: { type: "char" },
planned_count: { type: "integer"},
today_count: { type: "integer"},
overdue_count: { type: "integer"},
total_count: { type: "integer"},
+ active: { type: "boolean"}
},
records: [{
name: "Contact",
- res_model: "res.partner",
+ model: "res.partner",
planned_count: 0,
today_count: 1,
overdue_count: 0,
@@ -34,7 +35,7 @@ QUnit.module('ActivityMenu', {
},
{
name: "Task",
- res_model: "project.task",
+ model: "project.task",
planned_count: 1,
today_count: 0,
overdue_count: 0,
@@ -42,11 +43,18 @@ QUnit.module('ActivityMenu', {
},
{
name: "Issue",
- res_model: "project.issue",
+ model: "project.issue",
planned_count: 1,
today_count: 1,
overdue_count: 1,
total_count: 3,
+ },
+ {
+ model: null,
+ planned_count: 1,
+ today_count: 1,
+ overdue_count: 0,
+ total_count: 2,
}],
},
};
@@ -71,8 +79,8 @@ QUnit.test('activity menu widget: menu with no records', function (assert) {
activityMenu.destroy();
});
-QUnit.test('activity menu widget: activity menu with 3 records', function (assert) {
- assert.expect(10);
+QUnit.test('activity menu widget: activity menu with 4 records', function (assert) {
+ assert.expect(17);
var self = this;
var activityMenu = new systray.ActivityMenu();
testUtils.addMockEnvironment(activityMenu, {
@@ -81,6 +89,16 @@ QUnit.test('activity menu widget: activity menu with 3 records', function (asser
if (args.method === 'activity_user_count') {
return $.when(self.data['mail.activity.menu']['records']);
}
+ if (route === '/mail/activity/new') {
+ assert.deepEqual(args, {'note': 'New Reminder'}, 'Create reminder should get proper value');
+ _.each(self.data['mail.activity.menu'].records, function (record) {
+ if (record.model == null) {
+ record.today_count += 1;
+ record.total_count += 1;
+ }
+ });
+ return $.when();
+ }
return this._super(route, args);
},
});
@@ -88,7 +106,7 @@ QUnit.test('activity menu widget: activity menu with 3 records', function (asser
assert.ok(activityMenu.$el.hasClass('o_mail_navbar_item'), 'should be the instance of widget');
assert.ok(activityMenu.$('.o_mail_channel_preview').hasClass('o_mail_channel_preview'), "should instance of widget");
assert.ok(activityMenu.$('.o_notification_counter').hasClass('o_notification_counter'), "widget should have notification counter");
- assert.strictEqual(parseInt(activityMenu.el.innerText), 5, "widget should have 5 notification counter");
+ assert.strictEqual(parseInt(activityMenu.el.innerText), 7, "widget should have 7 notification counter");
var context = {};
testUtils.intercept(activityMenu, 'do_action', function(event) {
@@ -123,7 +141,21 @@ QUnit.test('activity menu widget: activity menu with 3 records', function (asser
activityMenu.$('.dropdown-toggle').click();
activityMenu.$(".o_mail_navbar_dropdown_channels > div[data-model_name='Issue']").click();
+ // toggle quick create for reminder
+ activityMenu.$('.dropdown-toggle').click();
+ assert.strictEqual(activityMenu.$('.o_reminder_show').hasClass("hidden"), false, 'ActivityMenu should have Add new reminder CTA');
+ activityMenu.$('.o_reminder_show').click();
+ assert.strictEqual(activityMenu.$('.o_reminder_show').hasClass("hidden"), true, 'ActivityMenu should hide CTA when entering a new reminder');
+ assert.strictEqual(activityMenu.$('.o_reminder').hasClass("hidden"), false, 'ActivityMenu should display input for new reminder');
+
+ // creating quick reminder
+ activityMenu.$("input.o_reminder_input").val("New Reminder");
+ activityMenu.$(".o_reminder_save").click();
+ assert.strictEqual(parseInt(activityMenu.el.innerText), 8, "widget should have 9 notifications (8 base + 1 new reminder)");
+ assert.strictEqual(activityMenu.$('.o_reminder_show').hasClass("hidden"), false, 'ActivityMenu add reminder button should be displayed');
+ assert.strictEqual(activityMenu.$('.o_reminder').hasClass("hidden"), true, 'ActivityMenu add reminder input should be hidden');
+
activityMenu.destroy();
});
});
-});
+});
\ No newline at end of file
diff --git a/addons/mail/views/mail_activity_views.xml b/addons/mail/views/mail_activity_views.xml
index 1d09f8baedf..c04f8c82105 100644
--- a/addons/mail/views/mail_activity_views.xml
+++ b/addons/mail/views/mail_activity_views.xml
@@ -182,4 +182,103 @@
+
+ mail.activity.view.form
+ mail.activity
+ 21
+
+
+
+
+
+
+ mail.activity.view.kanban
+ mail.activity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mail.activity.reminder.view.search
+ mail.activity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reminders
+ mail.activity
+ form
+
+
+
+ kanban,form
+ [('user_id', '=', uid), ('res_model', '=', False), ('res_id', 'in', [0,None])]
+
diff --git a/addons/test_mail/tests/test_mail_activity.py b/addons/test_mail/tests/test_mail_activity.py
index 2d21649b01b..cb1b0173d9f 100644
--- a/addons/test_mail/tests/test_mail_activity.py
+++ b/addons/test_mail/tests/test_mail_activity.py
@@ -8,7 +8,7 @@ from odoo import exceptions
from odoo.addons.test_mail.tests.common import BaseFunctionalTest
from odoo.tools import mute_logger
-
+
class TestMailActivity(BaseFunctionalTest):
@classmethod
@@ -24,7 +24,7 @@ class TestMailActivity(BaseFunctionalTest):
# employee record an activity and check the deadline
self.env['mail.activity'].create({
'summary': 'Test Activity',
- 'date_deadline': date.today() + relativedelta(days=1),
+ 'date_deadline': date.today() + relativedelta(days=1),
'activity_type_id': self.env.ref('mail.mail_activity_data_email').id,
'res_model_id': self.env['ir.model']._get(test_record._name).id,
'res_id': test_record.id,
@@ -32,11 +32,11 @@ class TestMailActivity(BaseFunctionalTest):
self.assertEqual(test_record.activity_summary, 'Test Activity')
self.assertEqual(test_record.activity_state, 'planned')
- test_record.activity_ids.write({'date_deadline': date.today() - relativedelta(days=1)})
+ test_record.activity_ids.write({'date_deadline': date.today() - relativedelta(days=1)})
test_record.invalidate_cache() # TDE note: should not have to do it I think
self.assertEqual(test_record.activity_state, 'overdue')
- test_record.activity_ids.write({'date_deadline': date.today()})
+ test_record.activity_ids.write({'date_deadline': date.today()})
test_record.invalidate_cache() # TDE note: should not have to do it I think
self.assertEqual(test_record.activity_state, 'today')
@@ -65,3 +65,51 @@ class TestMailActivity(BaseFunctionalTest):
'res_id': test_record.id,
})
# self.assertEqual(test_record.activity_ids, self.env['mail.activity'])
+ def test_reminder_security(self):
+ admin = self.env.ref('base.user_root')
+ demo = self.env.ref('base.user_demo')
+
+ reminder = self.env['mail.activity'].sudo(admin).create({
+ 'note': 'Test Reminder',
+ 'date_deadline': date.today() + relativedelta(days=1),
+ }) #try to delete record with demo user
+ with self.assertRaises(exceptions.AccessError):
+ #try to delete admin record with demo user
+ reminder.sudo(demo).unlink()
+
+ with self.assertRaises(exceptions.AccessError):
+ #try to update admin record with demo user
+ reminder.sudo(demo).write({'note': 'Give money to demo user'})
+
+ #but demo should be able to edit and delete its own reminder
+ demo_reminder = self.env['mail.activity'].sudo(demo).create({
+ 'note': 'Test Reminder demo',
+ 'date_deadline': date.today() + relativedelta(days=2),
+ })
+
+ demo_reminder.sudo(demo).write({'note': 'Holidays'})
+ self.assertEqual(demo_reminder.note, 'Holidays
')
+ #but edit or res_id and res_model should be impossible
+ with self.assertRaises(exceptions.AccessError):
+ demo_reminder.sudo(demo).write({'res_id': 1})
+ with self.assertRaises(exceptions.AccessError):
+ demo_reminder.sudo(demo).write({'res_model_id': 1})
+ #user can unlink own reminder
+ demo_reminder.sudo(demo).unlink()
+
+ def test_reminder_summary(self):
+ reminder = self.env['mail.activity'].create({
+ 'note': 'Test Reminder',
+ 'date_deadline': date.today(),
+ })
+ self.assertEqual(reminder.summary, 'Test Reminder', "Summary should be first line of note by default")
+ reminder.write({'note': 'Holidays\nDestination: Pescara'})
+ self.assertEqual(reminder.summary, 'Holidays', "Summary should be first line of note by default")
+ reminder.write({'note': ''})
+ self.assertEqual(reminder.summary, 'Reminder', "If note is empty, summary should be Reminder")
+ reminder.write({'note': 'Holidays', 'summary': 'Summary'})
+ self.assertEqual(reminder.summary, 'Summary', "If summary is set, shouldn't be changed")
+
+
+
+