From f488dc58c37fdc7fd49df759165de641e213444b Mon Sep 17 00:00:00 2001 From: Dharmang Soni Date: Thu, 28 Dec 2017 16:53:51 +0530 Subject: [PATCH] [IMP] mail: allow to use stand alone activities as reminders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit allows to create personal reminders and to manage them using activities. It is done using activities not necessarily linked to a record but only to a user. Reminders can be used for example as a personal todo list. Technically we choose to model a reminder as an activity related to no record. This means having a void res_id and model. An activity type is added to classify reminders in it. Settings reminder as done is not done like classic activities. Indeed classic activities are unlinked and a message is logged on the related document. As there is no document for reminders those are archived instead. It means an active field is added as well as an override of toggle_archive to update systray counter. Activities systray widget is improved to display reminders in activities list. This entry leads to a kanban view of activities allowing their management. It is also possible to create them directly from the systray allowing a quick creation of reminders. This commit is related to task ID 41745. Closes #22143 . Co-authored-by: Dharmang Soni Co-authored-by: Xavier Dollé --- addons/mail/__manifest__.py | 1 + addons/mail/controllers/main.py | 12 ++ addons/mail/data/mail_activity_data.xml | 6 + addons/mail/data/mail_activity_demo.xml | 10 ++ addons/mail/data/mail_demo.xml | 1 - addons/mail/models/mail_activity.py | 75 ++++++++-- addons/mail/models/res_users.py | 22 ++- addons/mail/static/src/img/reminder.png | Bin 0 -> 4953 bytes addons/mail/static/src/js/systray.js | 139 +++++++++++++++--- addons/mail/static/src/less/discuss.less | 23 ++- .../mail/static/src/less/mail_activity.less | 10 +- addons/mail/static/src/xml/systray.xml | 23 +++ addons/mail/static/tests/systray_tests.js | 48 +++++- addons/mail/views/mail_activity_views.xml | 99 +++++++++++++ addons/test_mail/tests/test_mail_activity.py | 56 ++++++- 15 files changed, 478 insertions(+), 47 deletions(-) create mode 100644 addons/mail/data/mail_activity_demo.xml create mode 100644 addons/mail/static/src/img/reminder.png 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 0000000000000000000000000000000000000000..a585b8d6e09421ec098d88df187ba0ecd67fc6cd GIT binary patch literal 4953 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 Xb9CCT0<2gz00000NkvXXu0mjf?bi@w literal 0 HcmV?d00001 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 @@ + +