diff options
Diffstat (limited to 'addons/note')
| -rw-r--r-- | addons/note/__init__.py | 3 | ||||
| -rw-r--r-- | addons/note/__manifest__.py | 5 | ||||
| -rw-r--r-- | addons/note/controllers/__init__.py | 4 | ||||
| -rw-r--r-- | addons/note/controllers/note.py | 27 | ||||
| -rw-r--r-- | addons/note/data/mail_activity_data.xml | 12 | ||||
| -rw-r--r-- | addons/note/models/__init__.py | 1 | ||||
| -rw-r--r-- | addons/note/models/mail_activity.py | 16 | ||||
| -rw-r--r-- | addons/note/models/res_users.py | 27 | ||||
| -rw-r--r-- | addons/note/static/src/js/systray.js | 138 | ||||
| -rw-r--r-- | addons/note/static/src/less/note.less | 38 | ||||
| -rw-r--r-- | addons/note/static/src/xml/systray.xml | 32 | ||||
| -rw-r--r-- | addons/note/static/tests/systray_tests.js | 66 | ||||
| -rw-r--r-- | addons/note/views/mail_activity_views.xml | 14 | ||||
| -rw-r--r-- | addons/note/views/note_templates.xml | 7 |
14 files changed, 388 insertions, 2 deletions
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 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data noupdate="1"> + <record id="mail_activity_data_reminder" model="mail.activity.type"> + <field name="name">Reminder</field> + <field name="category">reminder</field> + <field name="icon">fa-tasks</field> + <field name="days">0</field> + <field name="sequence">20</field> + </record> + </data> +</odoo> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates> + <t t-extend="mail.chat.ActivityMenuPreview"> + <t t-jquery="t[t-foreach*='activities'][t-as*='activity']" t-operation="after"> + <div class="o_note_show"> + <a class="btn btn-block text-center">Add new note</a> + </div> + <div class="o_note o_mail_channel_preview hidden"> + <div class="o_mail_channel_image o_mail_channel_app"> + <img src="/note/static/description/icon.png"/> + </div> + <div class="o_channel_info"> + <div class="o_channel_title"> + <span class="o_channel_name"><strong>Add a note</strong></span> + <div class="o_note_datetime"/> + <span class="ml4"> + <a class="o_note_set_datetime text-muted"> + <span class="fa fa-clock-o"/> + </a> + </span> + </div> + <div class="o_note_input_box"> + <p><input class="o_note_input" type="text" placeholder="Remember..." /></p> + <span class="ml8 mr4"> + <a class="o_note_save">SAVE</a> + </span> + </div> + </div> + </div> + </t> + </t> +</templates>
\ 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 @@ +<?xml version="1.0"?> +<odoo> + + <record id="mail_activity_type_view_form" model="ir.ui.view"> + <field name="name">mail.activity.type.form.inherit.note</field> + <field name="model">mail.activity.type</field> + <field name="inherit_id" ref="mail.mail_activity_type_view_form"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='category']" position="attributes"> + <attribute name="invisible">0</attribute> + </xpath> + </field> + </record> +</odoo>
\ 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 @@ <template id="assets_backend" inherit_id="web.assets_backend"> <xpath expr="link[last()]" position="after"> <link rel="stylesheet" type="text/less" href="/note/static/src/less/note.less"/> + <script type="text/javascript" src="/note/static/src/js/systray.js"></script> + </xpath> + </template> + + <template id="qunit_suite" name="note_tests" inherit_id="web.qunit_suite"> + <xpath expr="//t[@t-set='head']" position="inside"> + <script type="text/javascript" src="/note/static/tests/systray_tests.js"></script> </xpath> </template> </odoo> |
