diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py
index 4a23ef2573c..105d149fa43 100644
--- a/addons/mail/__manifest__.py
+++ b/addons/mail/__manifest__.py
@@ -38,7 +38,6 @@
],
'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 3445691f601..f0990c7de40 100644
--- a/addons/mail/controllers/main.py
+++ b/addons/mail/controllers/main.py
@@ -230,15 +230,3 @@ 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')], limit=1).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 35a103123ef..950a0794858 100644
--- a/addons/mail/data/mail_activity_data.xml
+++ b/addons/mail/data/mail_activity_data.xml
@@ -23,12 +23,5 @@
5
12
-
- Reminder
- reminder
- fa-tasks
- 0
- 15
-
diff --git a/addons/mail/data/mail_activity_demo.xml b/addons/mail/data/mail_activity_demo.xml
deleted file mode 100644
index f15a84331d4..00000000000
--- a/addons/mail/data/mail_activity_demo.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
- Lunch with colleagues this Sunday.
-
-
-
-
-
\ No newline at end of file
diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py
index 2d252f911c4..5da155e61ce 100644
--- a/addons/mail/models/mail_activity.py
+++ b/addons/mail/models/mail_activity.py
@@ -5,7 +5,6 @@ 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):
@@ -46,7 +45,7 @@ class MailActivityType(models.Model):
'mail.activity.type', 'mail_activity_rel', 'recommended_id', 'activity_id',
string='Preceding Activities')
category = fields.Selection([
- ('default', 'Other'), ('reminder', 'Reminder')], default='default',
+ ('default', 'Other')], default='default',
string='Category',
help='Categories may trigger specific behavior like opening calendar view')
@@ -70,10 +69,10 @@ class MailActivity(models.Model):
return res
# owner
- res_id = fields.Integer('Related Document ID', index=True)
+ res_id = fields.Integer('Related Document ID', index=True, required=True)
res_model_id = fields.Many2one(
'ir.model', 'Document Model',
- index=True, ondelete='cascade')
+ index=True, ondelete='cascade', required=True)
res_model = fields.Char(
'Related Document Model',
index=True, related='res_model_id.model', store=True, readonly=True)
@@ -90,9 +89,6 @@ 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.')
automated = fields.Boolean(
'Automated activity', readonly=True,
help='Indicates this activity has been created automatically and not by any user.')
@@ -122,8 +118,7 @@ class MailActivity(models.Model):
@api.depends('res_model', 'res_id')
def _compute_res_name(self):
for activity in self:
- if activity.res_model and activity.res_id:
- activity.res_name = self.env[activity.res_model].browse(activity.res_id).name_get()[0][1]
+ activity.res_name = self.env[activity.res_model].browse(activity.res_id).name_get()[0][1]
@api.depends('date_deadline')
def _compute_state(self):
@@ -173,14 +168,7 @@ class MailActivity(models.Model):
doc_operation = 'read' if operation == 'read' else 'write'
activity_to_documents = dict()
for activity in self.sudo():
- 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."))
-
+ activity_to_documents.setdefault(activity.res_model, list()).append(activity.res_id)
for model, res_ids in activity_to_documents.items():
self.env[model].check_access_rights(doc_operation, raise_exception=True)
try:
@@ -196,19 +184,11 @@ 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')
-
- # 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])
-
+ 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),
@@ -218,28 +198,13 @@ 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')
-
- 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)
+ res = super(MailActivity, self.sudo()).write(values)
if values.get('user_id'):
for activity in self:
- 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])
+ 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),
@@ -262,29 +227,6 @@ 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 5b7c82d6725..40032cac40f 100644
--- a/addons/mail/models/res_users.py
+++ b/addons/mail/models/res_users.py
@@ -70,35 +70,23 @@ class Users(models.Model):
@api.model
def activity_user_count(self):
- # 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,
+ 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
- LEFT OUTER JOIN ir_model AS m ON act.res_model_id = m.id
- WHERE user_id = %s AND active = TRUE
+ JOIN ir_model AS m ON act.res_model_id = m.id
+ WHERE user_id = %s
GROUP BY m.id, states, act.res_model;
"""
self.env.cr.execute(query, [self.env.uid])
activity_data = self.env.cr.dictfetchall()
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()}
+ model_names = {n[0]: n[1] for n in self.env['ir.model'].browse(model_ids).name_get()}
- # 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
- }
- }
+ user_activities = {}
for activity in activity_data:
if not user_activities.get(activity['model']):
user_activities[activity['model']] = {
diff --git a/addons/mail/static/src/img/reminder.png b/addons/mail/static/src/img/reminder.png
deleted file mode 100644
index a585b8d6e09..00000000000
Binary files a/addons/mail/static/src/img/reminder.png and /dev/null differ
diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js
index d382745df29..5a4c8ea4675 100644
--- a/addons/mail/static/src/js/systray.js
+++ b/addons/mail/static/src/js/systray.js
@@ -3,13 +3,11 @@ 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
@@ -159,13 +157,8 @@ var MessagingMenu = Widget.extend({
var ActivityMenu = Widget.extend({
template:'mail.chat.ActivityMenu',
events: {
- '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',
+ "click": "_onActivityMenuClick",
+ "click .o_mail_channel_preview": "_onActivityFilterClick",
},
start: function () {
this.$activities_preview = this.$('.o_mail_navbar_dropdown_channels');
@@ -191,21 +184,8 @@ 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;
-
- //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.activityCounter = _.reduce(data, 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);
});
@@ -261,27 +241,6 @@ 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
//-----------------------------------------------------------
@@ -300,20 +259,15 @@ var ActivityMenu = Widget.extend({
} else {
context['search_default_activities_' + data.filter] = 1;
}
- 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,
- });
- }
+ 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
@@ -325,65 +279,6 @@ 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 b2bfc6236c8..4e0ae26dccc 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(1, 1, auto);
+ .o-flex(0, 1, auto);
.o-text-overflow();
}
.o_channel_counter {
@@ -428,27 +428,6 @@
}
}
-.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 f98e0974b7b..5f1320ce402 100644
--- a/addons/mail/static/src/less/mail_activity.less
+++ b/addons/mail/static/src/less/mail_activity.less
@@ -132,15 +132,6 @@
.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 e70d36ed4c1..dd61c486b9b 100644
--- a/addons/mail/static/src/xml/systray.xml
+++ b/addons/mail/static/src/xml/systray.xml
@@ -80,29 +80,6 @@
-
-
-
-

-
-
-
-
Set a reminder
-
-
-
-
-
-
-
- SAVE
-
-
-
-
-
diff --git a/addons/mail/static/tests/systray_tests.js b/addons/mail/static/tests/systray_tests.js
index 7ad26493cb2..f8585d37699 100644
--- a/addons/mail/static/tests/systray_tests.js
+++ b/addons/mail/static/tests/systray_tests.js
@@ -23,7 +23,6 @@ QUnit.module('ActivityMenu', {
today_count: { type: "integer"},
overdue_count: { type: "integer"},
total_count: { type: "integer"},
- active: { type: "boolean"}
},
records: [{
name: "Contact",
@@ -48,13 +47,6 @@ QUnit.module('ActivityMenu', {
today_count: 1,
overdue_count: 1,
total_count: 3,
- },
- {
- model: null,
- planned_count: 1,
- today_count: 1,
- overdue_count: 0,
- total_count: 2,
}],
},
};
@@ -79,8 +71,8 @@ QUnit.test('activity menu widget: menu with no records', function (assert) {
activityMenu.destroy();
});
-QUnit.test('activity menu widget: activity menu with 4 records', function (assert) {
- assert.expect(17);
+QUnit.test('activity menu widget: activity menu with 3 records', function (assert) {
+ assert.expect(10);
var self = this;
var activityMenu = new systray.ActivityMenu();
testUtils.addMockEnvironment(activityMenu, {
@@ -89,16 +81,6 @@ QUnit.test('activity menu widget: activity menu with 4 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);
},
});
@@ -106,7 +88,7 @@ QUnit.test('activity menu widget: activity menu with 4 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), 7, "widget should have 7 notification counter");
+ assert.strictEqual(parseInt(activityMenu.el.innerText), 5, "widget should have 5 notification counter");
var context = {};
testUtils.intercept(activityMenu, 'do_action', function(event) {
@@ -141,20 +123,6 @@ QUnit.test('activity menu widget: activity menu with 4 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();
});
});
diff --git a/addons/mail/views/mail_activity_views.xml b/addons/mail/views/mail_activity_views.xml
index c04f8c82105..1d09f8baedf 100644
--- a/addons/mail/views/mail_activity_views.xml
+++ b/addons/mail/views/mail_activity_views.xml
@@ -182,103 +182,4 @@
-
- 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/note/__init__.py b/addons/note/__init__.py
index dc5e6b693d1..3ea0f0cac45 100644
--- a/addons/note/__init__.py
+++ b/addons/note/__init__.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import models
+from . import controllers
+from . import models
\ No newline at end of file
diff --git a/addons/note/__manifest__.py b/addons/note/__manifest__.py
index 7a0edd704a9..02cb893ca1d 100644
--- a/addons/note/__manifest__.py
+++ b/addons/note/__manifest__.py
@@ -15,13 +15,18 @@
'data': [
'security/note_security.xml',
'security/ir.model.access.csv',
+ 'data/mail_activity_data.xml',
'data/note_data.xml',
'views/note_views.xml',
'views/note_templates.xml',
+ 'views/mail_activity_views.xml',
],
'demo': [
'data/note_demo.xml',
],
+ 'qweb': [
+ 'static/src/xml/systray.xml',
+ ],
'test': [
],
'installable': True,
diff --git a/addons/note/controllers/__init__.py b/addons/note/controllers/__init__.py
new file mode 100644
index 00000000000..4d2e8b5ad27
--- /dev/null
+++ b/addons/note/controllers/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import note
\ No newline at end of file
diff --git a/addons/note/controllers/note.py b/addons/note/controllers/note.py
new file mode 100644
index 00000000000..bdcf2e85059
--- /dev/null
+++ b/addons/note/controllers/note.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import http
+from odoo.http import request
+
+
+class NoteController(http.Controller):
+
+ @http.route('/note/new', type='json', auth='user')
+ def note_new_from_systray(self, note, activity_type_id=None, date_deadline=None):
+ """ Route to create note and their activity directly from the systray """
+ note = request.env['note.note'].create({'memo': note})
+ if date_deadline:
+ activity_values = {
+ 'note': note.memo,
+ 'date_deadline': date_deadline,
+ 'res_model_id': request.env.ref("note.model_note_note").id,
+ 'res_id': note.id,
+ 'note_id': note.id,
+ }
+ if not activity_type_id:
+ activity_type_id = request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')], limit=1).id
+ if activity_type_id:
+ activity_values['activity_type_id'] = activity_type_id
+ request.env['mail.activity'].create(activity_values)
+ return note.id
diff --git a/addons/note/data/mail_activity_data.xml b/addons/note/data/mail_activity_data.xml
new file mode 100644
index 00000000000..5b2a5e6c905
--- /dev/null
+++ b/addons/note/data/mail_activity_data.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ Reminder
+ reminder
+ fa-tasks
+ 0
+ 20
+
+
+
diff --git a/addons/note/models/__init__.py b/addons/note/models/__init__.py
index 5c651fca9a0..db6de6e3c7f 100644
--- a/addons/note/models/__init__.py
+++ b/addons/note/models/__init__.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from . import mail_activity
from . import note
from . import res_users
diff --git a/addons/note/models/mail_activity.py b/addons/note/models/mail_activity.py
new file mode 100644
index 00000000000..67f7ea82966
--- /dev/null
+++ b/addons/note/models/mail_activity.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, fields
+
+
+class MailActivityType(models.Model):
+ _inherit = "mail.activity.type"
+
+ category = fields.Selection(selection_add=[('reminder', 'Reminder')])
+
+
+class MailActivity(models.Model):
+ _inherit = "mail.activity"
+
+ note_id = fields.Many2one('note.note', string="Related Note", ondelete='cascade')
diff --git a/addons/note/models/res_users.py b/addons/note/models/res_users.py
index cfeba15922f..be218c27b28 100644
--- a/addons/note/models/res_users.py
+++ b/addons/note/models/res_users.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, models
+from odoo import api, models, modules, _
class Users(models.Model):
@@ -19,3 +19,28 @@ class Users(models.Model):
if stage:
stage.sudo().copy(default={'user_id': user.id})
return user
+
+ @api.model
+ def activity_user_count(self):
+ """ If user have not scheduled any note, it will not appear in activity menu.
+ Making note activity always visible with number of notes on label. If there is no notes,
+ activity menu not visible for note.
+ """
+ activities = super(Users, self).activity_user_count()
+ notes_count = self.env['note.note'].search_count([('user_id', '=', self.env.uid), ('open', '=', True)])
+ if notes_count:
+ note_index = next((index for (index, a) in enumerate(activities) if a["model"] == "note.note"), None)
+ note_label = ("%s (%d)") % (_('Notes'), notes_count)
+ if note_index is not None:
+ activities[note_index]['name'] = note_label
+ else:
+ activities.append({
+ 'name': note_label,
+ 'model': 'note.note',
+ 'icon': modules.module.get_module_icon(self.env['note.note']._original_module),
+ 'total_count': 0,
+ 'today_count': 0,
+ 'overdue_count': 0,
+ 'planned_count': 0
+ })
+ return activities
diff --git a/addons/note/static/src/js/systray.js b/addons/note/static/src/js/systray.js
new file mode 100644
index 00000000000..54a2fe6bdd1
--- /dev/null
+++ b/addons/note/static/src/js/systray.js
@@ -0,0 +1,138 @@
+odoo.define('note.systray', function (require) {
+"use strict";
+
+var core = require('web.core');
+var ActivityMenu = require('mail.systray').ActivityMenu;
+var datepicker = require('web.datepicker');
+
+var _t = core._t;
+
+ActivityMenu.include({
+ events: _.extend({}, ActivityMenu.prototype.events, {
+ 'click .o_note_show': '_onAddNoteClick',
+ 'click .o_note_save': '_onNoteSaveClick',
+ 'click .o_note_set_datetime': '_onNoteDateTimeSetClick',
+ 'keydown input.o_note_input': '_onNoteInputKeyDown',
+ 'click .o_note': '_onNewNoteClick',
+ }),
+ //--------------------------------------------------
+ // Private
+ //--------------------------------------------------
+ /**
+ * Moving notes at first place
+ * @override
+ */
+ _getActivityData: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function() {
+ var reminderIndex = _.findIndex(self.activities, function (val) { return val.model == 'note.note'; });
+ if (reminderIndex > 0) {
+ self.activities.splice(0, 0, self.activities.splice(reminderIndex, 1)[0]);
+ }
+ });
+ },
+ /**
+ * Save the note to database using datepicker date and field as note
+ * @private
+ */
+ _saveNote: function () {
+ var note = this.$('.o_note_input').val().trim();
+ if (! note) {
+ return;
+ }
+ var params = {'note': note};
+ var noteDateTime = this.noteDateTimeWidget.getValue();
+ if (noteDateTime) {
+ params = _.extend(params, {'date_deadline': noteDateTime});
+ }
+ this.$('.o_note_show').removeClass('hidden');
+ this.$('.o_note').addClass('hidden');
+ this._rpc({
+ route: '/note/new',
+ params: params,
+ }).then(this._updateActivityPreview.bind(this));
+ },
+ //-----------------------------------------
+ // Handlers
+ //-----------------------------------------
+ /**
+ * @override
+ */
+ _onActivityFilterClick: function (ev) {
+ var $el = $(ev.currentTarget);
+ if(!$el.hasClass("o_note")) {
+ var data = _.extend({}, $el.data(), $(ev.target).data());
+ if (data.res_model === "note.note" && data.filter === "my") {
+ this.do_action({
+ type: 'ir.actions.act_window',
+ name: data.model_name,
+ res_model: data.res_model,
+ views: [[false, 'kanban'], [false, 'form'], [false, 'list']]
+ });
+ } else {
+ this._super.apply(this, arguments);
+ }
+ }
+ },
+ /**
+ * When add new note button clicked, toggling quick note create view inside
+ * Systray activity view
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onAddNoteClick: function (ev) {
+ ev.stopPropagation();
+ if (!this.noteDateTimeWidget){
+ this.noteDateTimeWidget = new datepicker.DateWidget(this, {useCurrent: true});
+ }
+ this.noteDateTimeWidget.appendTo(this.$('.o_note_datetime'));
+ this.noteDateTimeWidget.$input.attr('placeholder', _t("Today"));
+ this.$('.o_note_show, .o_note').toggleClass('hidden');
+ this.$('.o_note_input').val('').focus();
+
+ },
+ /**
+ * When focusing on input for new quick note systerm tray must be open.
+ * Preventing to close
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onNewNoteClick: function (ev) {
+ ev.stopPropagation();
+ },
+ /**
+ * Opens datetime picker for note.
+ * Quick FIX due to no option for set custom icon instead of caret in datepicker.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onNoteDateTimeSetClick: function (ev) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ this.noteDateTimeWidget.$input.click();
+ },
+ /**
+ * Saving note (quick create) and updating activity preview
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onNoteSaveClick: function (ev) {
+ this._saveNote();
+ },
+ /**
+ * Handling Enter key for quick create note.
+ *
+ * @private
+ * @param {KeyboardEvent} ev
+ */
+ _onNoteInputKeyDown: function (ev) {
+ if (ev.which === $.ui.keyCode.ENTER) {
+ this._saveNote();
+ }
+ },
+});
+});
diff --git a/addons/note/static/src/less/note.less b/addons/note/static/src/less/note.less
index c92c5cd0500..926e7ddb913 100644
--- a/addons/note/static/src/less/note.less
+++ b/addons/note/static/src/less/note.less
@@ -23,3 +23,41 @@
}
}
}
+
+// Quick create notes from systray
+.o_note.o_mail_channel_preview {
+ background-color: white;
+ .o_channel_info {
+ .o_channel_title {
+ .o_channel_name {
+ .o-flex(1, 1, 100%);
+ }
+ }
+ .o_note_input_box {
+ .o-flex-display();
+ p {
+ .o-flex(1, 1, auto);
+ }
+ }
+ .o_note_save {
+ font-size: 11px;
+ font-weight: bold;
+ }
+ }
+ .o_note_input {
+ border: none;
+ }
+ .o_note_datetime {
+ .o_datepicker {
+ .o_datepicker_input {
+ float: right;
+ text-align: right;
+ border: none;
+ font-size: 11px;
+ }
+ .o_datepicker_button {
+ display: none;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/addons/note/static/src/xml/systray.xml b/addons/note/static/src/xml/systray.xml
new file mode 100644
index 00000000000..4b2052c3b85
--- /dev/null
+++ b/addons/note/static/src/xml/systray.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+

+
+
+
+
Add a note
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/note/static/tests/systray_tests.js b/addons/note/static/tests/systray_tests.js
new file mode 100644
index 00000000000..2b07bf85757
--- /dev/null
+++ b/addons/note/static/tests/systray_tests.js
@@ -0,0 +1,66 @@
+odoo.define('note.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('note', {}, function () {
+
+QUnit.module("ActivityMenu", {
+ beforeEach: function () {
+ this.services = [ChatManager, createBusService()];
+ this.data = {
+ 'mail.activity.menu': {
+ records: [],
+ },
+ };
+ }
+});
+
+QUnit.test('note activity menu widget: create note from activity menu', 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 === 'activity_user_count') {
+ return $.when(self.data['mail.activity.menu']['records']);
+ }
+ if (route === '/note/new') {
+ return $.when();
+ }
+ return this._super(route, args);
+ },
+ });
+ activityMenu.appendTo($('#qunit-fixture'));
+ assert.ok(activityMenu.$el.hasClass('o_mail_navbar_item'), 'should be the instance of widget');
+
+ // toggle quick create for note
+ var step = 1;
+ activityMenu.$('.dropdown-toggle').click();
+ assert.strictEqual(activityMenu.$('.o_note_show').hasClass("hidden"), false, 'ActivityMenu should have Add new note CTA');
+ activityMenu.$('.o_note_show').click();
+ assert.strictEqual(activityMenu.$('.o_note_show').hasClass("hidden"), true, 'ActivityMenu should hide CTA when entering a new note');
+ assert.strictEqual(activityMenu.$('.o_note').hasClass("hidden"), false, 'ActivityMenu should display input for new note');
+
+ // creating quick note without date
+ activityMenu.$("input.o_note_input").val("New Note");
+ activityMenu.$(".o_note_save").click();
+ assert.strictEqual(activityMenu.$('.o_note_show').hasClass("hidden"), false, 'ActivityMenu add note button should be displayed');
+ assert.strictEqual(activityMenu.$('.o_note').hasClass("hidden"), true, 'ActivityMenu add note input should be hidden');
+
+ // creating quick note with date
+ step = 2;
+ activityMenu.$('.o_note_show').click();
+ activityMenu.$('input.o_note_input').val("New Note");
+ activityMenu.$('.o_note_set_datetime').click();
+ activityMenu.$(".o_note_save").click();
+ assert.strictEqual(activityMenu.$('.o_note_show').hasClass("hidden"), false, 'ActivityMenu add note button should be displayed');
+ assert.strictEqual(activityMenu.$('.o_note').hasClass("hidden"), true, 'ActivityMenu add note input should be hidden');
+ activityMenu.destroy();
+});
+});
+});
diff --git a/addons/note/views/mail_activity_views.xml b/addons/note/views/mail_activity_views.xml
new file mode 100644
index 00000000000..bc980423e3f
--- /dev/null
+++ b/addons/note/views/mail_activity_views.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ mail.activity.type.form.inherit.note
+ mail.activity.type
+
+
+
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/addons/note/views/note_templates.xml b/addons/note/views/note_templates.xml
index 3b6c3e1acc4..c80d00d84e0 100644
--- a/addons/note/views/note_templates.xml
+++ b/addons/note/views/note_templates.xml
@@ -3,6 +3,13 @@
+
+
+
+
+
+
+
diff --git a/addons/test_mail/tests/test_mail_activity.py b/addons/test_mail/tests/test_mail_activity.py
index e340358a5ee..5a4121f2c6c 100644
--- a/addons/test_mail/tests/test_mail_activity.py
+++ b/addons/test_mail/tests/test_mail_activity.py
@@ -69,50 +69,6 @@ class TestMailActivity(BaseFunctionalTest):
'res_id': test_record.id,
})
- def test_reminder_security(self):
- reminder = self.env['mail.activity'].sudo(self.user_admin).create({
- 'note': 'Test Reminder',
- 'date_deadline': date.today() + relativedelta(days=1),
- })
-
- with self.assertRaises(exceptions.AccessError):
- # try to delete admin record with demo user
- reminder.sudo(self.user_employee).unlink()
-
- with self.assertRaises(exceptions.AccessError):
- # try to update admin record with demo user
- reminder.sudo(self.user_employee).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(self.user_employee).create({
- 'note': 'Test Reminder demo',
- 'date_deadline': date.today() + relativedelta(days=2),
- })
- demo_reminder.write({'note': 'Holidays
'})
- self.assertEqual(demo_reminder.note, 'Holidays
')
-
- # edit of res_id and res_model should be impossible
- with self.assertRaises(exceptions.AccessError):
- demo_reminder.write({'res_id': 1})
- with self.assertRaises(exceptions.AccessError):
- demo_reminder.write({'res_model_id': 1})
-
- demo_reminder.unlink()
-
- def test_reminder_flow(self):
- with self.sudoAs('ernest'):
- reminder = self.env['mail.activity'].create({
- 'note': 'Test Reminder',
- 'date_deadline': date.today(),
- })
- self.assertEqual(reminder.summary, 'Test Reminder')
- reminder.write({'note': 'Holidays\nDestination: Pescara'})
- self.assertEqual(reminder.summary, 'Holidays')
- reminder.write({'note': ''})
- self.assertEqual(reminder.summary, 'Reminder')
- reminder.write({'note': 'Holidays', 'summary': 'Summary'})
- self.assertEqual(reminder.summary, 'Summary')
-
def test_activity_mixin(self):
today = date.today()
with self.sudoAs('ernest'):