diff --git a/addons/note/__init__.py b/addons/note/__init__.py
index 3ea0f0cac45..dc5e6b693d1 100644
--- a/addons/note/__init__.py
+++ b/addons/note/__init__.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import controllers
-from . import models
\ No newline at end of file
+from . import models
diff --git a/addons/note/__manifest__.py b/addons/note/__manifest__.py
index 9036a7a9869..45d535c2c9b 100644
--- a/addons/note/__manifest__.py
+++ b/addons/note/__manifest__.py
@@ -14,7 +14,6 @@
'data': [
'security/note_security.xml',
'security/ir.model.access.csv',
- 'data/mail_activity_type_data.xml',
'data/note_data.xml',
'data/res_users_data.xml',
'views/note_views.xml',
@@ -28,9 +27,6 @@
'web.assets_backend': [
'note/static/src/**/*',
],
- 'web.qunit_suite_tests': [
- 'note/static/tests/**/*',
- ],
},
'license': 'LGPL-3',
}
diff --git a/addons/note/controllers/note.py b/addons/note/controllers/note.py
deleted file mode 100644
index 24db9d49e96..00000000000
--- a/addons/note/controllers/note.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- 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:
- note.activity_schedule(
- activity_type_id=activity_type_id or request.env['mail.activity.type'].search([('category', '=', 'reminder')], limit=1).id,
- note=note.memo,
- date_deadline=date_deadline
- )
- return note.id
diff --git a/addons/note/models/__init__.py b/addons/note/models/__init__.py
index 11615bf4301..5c651fca9a0 100644
--- a/addons/note/models/__init__.py
+++ b/addons/note/models/__init__.py
@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import mail_activity
-from . import mail_activity_type
from . import note
from . import res_users
diff --git a/addons/note/models/mail_activity.py b/addons/note/models/mail_activity.py
deleted file mode 100644
index 0b03a60ff5f..00000000000
--- a/addons/note/models/mail_activity.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import models, fields
-
-
-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 5e1e9a772fc..62be55b4a33 100644
--- a/addons/note/models/res_users.py
+++ b/addons/note/models/res_users.py
@@ -45,30 +45,3 @@ GROUP BY id"""
stage.sudo().copy(default={'user_id': user.id})
else:
_logger.debug("Created note columns for %s", self)
-
- @api.model
- def systray_get_activities(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).systray_get_activities()
- notes_count = self.env['note.note'].search_count([('user_id', '=', self.env.uid)])
- if notes_count:
- note_index = next((index for (index, a) in enumerate(activities) if a["model"] == "note.note"), None)
- note_label = _('Notes')
- if note_index is not None:
- activities[note_index]['name'] = note_label
- else:
- activities.append({
- 'id': self.env['ir.model']._get('note.note').id,
- 'type': 'activity',
- '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/tests/helpers/mock_server/models/res_users.js b/addons/note/static/tests/helpers/mock_server/models/res_users.js
deleted file mode 100644
index 03192e1d044..00000000000
--- a/addons/note/static/tests/helpers/mock_server/models/res_users.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/** @odoo-module **/
-
-// ensure mail override is applied first.
-import '@mail/../tests/helpers/mock_server/models/res_users';
-
-import { patch } from '@web/core/utils/patch';
-import { MockServer } from '@web/../tests/helpers/mock_server';
-
-patch(MockServer.prototype, 'note/models/res_users', {
- /**
- * Simulates `systray_get_activities` on `res.users`.
- *
- * @override
- */
- _mockResUsersSystrayGetActivities() {
- const activities = this._super(...arguments);
- const noteCount = this.pyEnv['note.note'].searchCount([['user_id', '=', this.currentUserId]]);
- if (noteCount) {
- const noteIndex = activities.findIndex(act => act['model'] === 'note.note');
- if (noteIndex) {
- activities[noteIndex]['name'] = 'Notes';
- } else {
- activities.push({
- id: 'note.note', // for simplicity
- type: 'activity',
- name: 'Notes',
- model: 'note.note',
- planned_count: 0,
- today_count: 0,
- overdue_count: 0,
- total_count: 0,
- });
- }
- }
- return activities;
- },
-});
diff --git a/addons/note/views/note_views.xml b/addons/note/views/note_views.xml
index 582cdb628a5..0527d5db5d4 100644
--- a/addons/note/views/note_views.xml
+++ b/addons/note/views/note_views.xml
@@ -171,6 +171,9 @@
+
+
The Notes app is deprecated and has been replaced by Todo. All your existing notes have been converted to to-dos that you will find in this new app. The Notes app will be removed in the future. It is therefore adviced to not use it anymore as all the notes that would be created/modified in it will be lost during future upgrade.
+
@@ -225,27 +228,4 @@
-
-
diff --git a/addons/project/models/project_task.py b/addons/project/models/project_task.py
index fc54bc051bb..4a8b9d12a1e 100644
--- a/addons/project/models/project_task.py
+++ b/addons/project/models/project_task.py
@@ -91,7 +91,8 @@ class Task(models.Model):
@api.model
def _default_personal_stage_type_id(self):
- return self.env['project.task.type'].search([('user_id', '=', self.env.user.id)], limit=1).id
+ default_id = self.env.context.get('default_personal_stage_type_ids')
+ return (default_id or self.env['project.task.type'].search([('user_id', '=', self.env.user.id)], limit=1).ids or [False])[0]
@api.model
def _default_company_id(self):
diff --git a/addons/test_discuss_full/__manifest__.py b/addons/test_discuss_full/__manifest__.py
index 7e9acd93eeb..eb69c9871a1 100644
--- a/addons/test_discuss_full/__manifest__.py
+++ b/addons/test_discuss_full/__manifest__.py
@@ -17,6 +17,7 @@
'mail',
'mail_bot',
'note',
+ 'project_todo',
'website_livechat',
],
'installable': True,
diff --git a/addons/todo/__init__.py b/addons/todo/__init__.py
new file mode 100644
index 00000000000..c50aeb7438d
--- /dev/null
+++ b/addons/todo/__init__.py
@@ -0,0 +1,11 @@
+from . import controllers
+from . import models
+
+def _todo_uninstall(env):
+ # The record rule project.task_visibility_rule needs to apply to all rights and not just read after uninstallation
+ project_task_visibility_rule_rec = env.ref("project.task_visibility_rule")
+ project_task_visibility_rule_rec.write({
+ 'perm_create': True,
+ 'perm_unlink': True,
+ 'perm_write': True,
+ })
diff --git a/addons/todo/__manifest__.py b/addons/todo/__manifest__.py
new file mode 100644
index 00000000000..ba50ea0aaf5
--- /dev/null
+++ b/addons/todo/__manifest__.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'To-Do',
+ 'version': '1.0',
+ 'category': 'Productivity/To-Do',
+ 'summary': 'Organize your work with memos and to-do lists',
+ 'sequence': 260,
+ 'depends': [
+ 'project',
+ ],
+ 'auto_install': True,
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'security/todo_security.xml',
+ 'data/mail_activity_type_data.xml',
+ 'views/project_task_views.xml',
+ 'views/todo_menus.xml',
+ ],
+ 'installable': True,
+ 'application': True,
+ 'uninstall_hook': '_todo_uninstall',
+ 'assets': {
+ 'web.assets_backend': [
+ 'todo/static/src/scss/todo.scss',
+ 'todo/static/src/views/**/*',
+ 'todo/static/src/web/**/*',
+ ],
+ 'web.qunit_suite_tests': [
+ 'todo/static/tests/**/*',
+ ],
+ },
+ 'license': 'LGPL-3',
+}
diff --git a/addons/note/controllers/__init__.py b/addons/todo/controllers/__init__.py
similarity index 84%
rename from addons/note/controllers/__init__.py
rename to addons/todo/controllers/__init__.py
index 4d2e8b5ad27..d6a5545a3da 100644
--- a/addons/note/controllers/__init__.py
+++ b/addons/todo/controllers/__init__.py
@@ -1,4 +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
+from . import todo
diff --git a/addons/todo/controllers/todo.py b/addons/todo/controllers/todo.py
new file mode 100644
index 00000000000..8d69d4cec70
--- /dev/null
+++ b/addons/todo/controllers/todo.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.http import Controller, request, route
+
+class TodoController(Controller):
+
+ @route('/project_todo/new', type='json', auth='user')
+ def todo_new_from_systray(self, todo_description, activity_type_id=None, date_deadline=None):
+ """ Route to create todo and their activity directly from the systray """
+ todo = request.env['project.task'].create({'description': todo_description})
+ if date_deadline:
+ todo.activity_schedule(
+ activity_type_id=activity_type_id or request.env['mail.activity.type'].search([('category', '=', 'reminder')], limit=1).id,
+ note=todo.description,
+ date_deadline=date_deadline
+ )
+ return todo.id
diff --git a/addons/note/data/mail_activity_type_data.xml b/addons/todo/data/mail_activity_type_data.xml
similarity index 100%
rename from addons/note/data/mail_activity_type_data.xml
rename to addons/todo/data/mail_activity_type_data.xml
diff --git a/addons/todo/models/__init__.py b/addons/todo/models/__init__.py
new file mode 100644
index 00000000000..9df39df10f8
--- /dev/null
+++ b/addons/todo/models/__init__.py
@@ -0,0 +1,2 @@
+from . import mail_activity_type
+from . import project_task
diff --git a/addons/note/models/mail_activity_type.py b/addons/todo/models/mail_activity_type.py
similarity index 100%
rename from addons/note/models/mail_activity_type.py
rename to addons/todo/models/mail_activity_type.py
diff --git a/addons/todo/models/project_task.py b/addons/todo/models/project_task.py
new file mode 100644
index 00000000000..00f014b4c58
--- /dev/null
+++ b/addons/todo/models/project_task.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models, _
+from odoo.tools import html2plaintext
+
+class Task(models.Model):
+ _inherit = 'project.task'
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if not vals.get('name') and not vals.get('project_id') and not vals.get('parent_id'):
+ if vals.get('description'):
+ # Generating name from first line of the description
+ text = html2plaintext(vals['description'])
+ name = text.strip().replace('*', '').partition("\n")[0]
+ vals['name'] = (name[:97] + '...') if len(name) > 100 else name
+ else:
+ vals['name'] = _('Untitled to-do')
+ return super().create(vals_list)
diff --git a/addons/todo/security/ir.model.access.csv b/addons/todo/security/ir.model.access.csv
new file mode 100644
index 00000000000..452e7f160e7
--- /dev/null
+++ b/addons/todo/security/ir.model.access.csv
@@ -0,0 +1,4 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_project_task_type_user,project.task.type.user,project.model_project_task_type,base.group_user,1,1,1,1
+access_task_on_partner,project.task on partners,project.model_project_task,base.group_user,1,1,1,1
+access_project_tags_user,project.project_tags_user,project.model_project_tags,base.group_user,1,1,1,1
diff --git a/addons/todo/security/todo_security.xml b/addons/todo/security/todo_security.xml
new file mode 100644
index 00000000000..ec80ae8bea4
--- /dev/null
+++ b/addons/todo/security/todo_security.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+ Project/Task: employees: Full access to own private task only
+
+ [('project_id', '=', False), ('user_ids', 'in', user.id), ('parent_id', '=', False)]
+
+
+
+
+ Project/Task: project users: follow required for follower-only projects
+
+ [
+ '|',
+ '&',
+ ('project_id', '!=', False),
+ '|',
+ ('project_id.privacy_visibility', '!=', 'followers'),
+ ('project_id.message_partner_ids', 'in', [user.partner_id.id]),
+ '|',
+ ('message_partner_ids', 'in', [user.partner_id.id]),
+ # to subscribe check access to the record, follower is not enough at creation
+ ('user_ids', 'in', user.id)
+ ]
+
+
+
+
+
diff --git a/addons/todo/static/description/icon.png b/addons/todo/static/description/icon.png
new file mode 100644
index 00000000000..916b9717cb7
Binary files /dev/null and b/addons/todo/static/description/icon.png differ
diff --git a/addons/todo/static/description/icon.svg b/addons/todo/static/description/icon.svg
new file mode 100644
index 00000000000..6b7a2a1ec8a
--- /dev/null
+++ b/addons/todo/static/description/icon.svg
@@ -0,0 +1 @@
+
diff --git a/addons/todo/static/src/scss/todo.scss b/addons/todo/static/src/scss/todo.scss
new file mode 100644
index 00000000000..469a67aa3d4
--- /dev/null
+++ b/addons/todo/static/src/scss/todo.scss
@@ -0,0 +1,40 @@
+.o_todo_form_view {
+ .o_form_sheet_bg {
+ display: flex;
+ flex-direction: column;
+ .o_form_statusbar {
+ margin: 0;
+ .o_field_widget {
+ align-self: center !important;
+ }
+ }
+ .o_form_sheet {
+ flex: 1;
+ max-width: 100%;
+ .oe_pad {
+ flex: 1;
+ margin-bottom: 0;
+ display: flex;
+ flex-direction: column;
+ .oe_pad_content {
+ border: none;
+ flex: 1;
+ }
+ }
+ }
+ .o_field_html .note-editable {
+ border: none !important;
+ padding: $o-sheet-vpadding $o-horizontal-padding 10px !important;
+ min-height: 300px;
+ }
+ }
+
+ &.o_form_readonly {
+ .oe_description {
+ padding: $o-sheet-vpadding $o-horizontal-padding;
+ }
+ }
+}
+.o_todo_kanban_card_body {
+ padding-left: 9px;
+}
diff --git a/addons/todo/static/src/views/todo_form/todo_form_controller.scss b/addons/todo/static/src/views/todo_form/todo_form_controller.scss
new file mode 100644
index 00000000000..c7fd3eaf592
--- /dev/null
+++ b/addons/todo/static/src/views/todo_form/todo_form_controller.scss
@@ -0,0 +1,11 @@
+.o_form_view.o_xxl_form_view {
+
+ .o_todo_form_sheet_bg {
+
+ > .o_form_statusbar, > .alert {
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ }
+}
diff --git a/addons/note/static/src/web/activity/activity_menu_patch.js b/addons/todo/static/src/web/activity/activity_menu_patch.js
similarity index 52%
rename from addons/note/static/src/web/activity/activity_menu_patch.js
rename to addons/todo/static/src/web/activity/activity_menu_patch.js
index 474dadf10e7..aa8b1832495 100644
--- a/addons/note/static/src/web/activity/activity_menu_patch.js
+++ b/addons/todo/static/src/web/activity/activity_menu_patch.js
@@ -7,63 +7,68 @@ import { DateTimeInput } from "@web/core/datetime/datetime_input";
import { useService } from "@web/core/utils/hooks";
import { patch } from "@web/core/utils/patch";
-patch(ActivityMenu.prototype, "note", {
+patch(ActivityMenu.prototype, "todo", {
setup() {
this._super(...arguments);
this.rpc = useService("rpc");
- this.state = useState({ addingNote: false });
- this.noteInputRef = useRef("noteInput");
- this.addingNoteDate = false;
+ this.state = useState({ addingTodo: false });
+ this.todoInputRef = useRef("todoInput");
+ this.addingTodoDate = false;
+ this.notification = useService("notification");
useEffect(
- (addingNote) => {
- if (addingNote) {
- this.noteInputRef.el.focus();
+ (addingTodo) => {
+ if (addingTodo) {
+ this.todoInputRef.el.focus();
}
},
- () => [this.state.addingNote]
+ () => [this.state.addingTodo]
);
- onExternalClick("noteInput", (ev) => {
+ onExternalClick("todoInput", (ev) => {
if (
ev.target.closest(".o-mail-ActivityMenu-show") ||
ev.target.closest(".o_datetime_picker")
) {
return;
}
- this.state.addingNote = false;
+ this.state.addingTodo = false;
});
},
sortActivityGroups() {
this._super();
this.store.activityGroups.sort((g1, g2) => {
- if (g1.model === "note.note" ? true : false) {
+ if (g1.model === "project.task" ? true : false) {
return -1;
}
- if (g2.model === "note.note" ? true : false) {
+ if (g2.model === "project.task" ? true : false) {
return 1;
}
});
},
- onKeydownNoteInput(ev) {
+ onKeydownTodoInput(ev) {
if (ev.key === "Enter") {
- this.saveNote();
+ this.saveTodo();
}
},
- async saveNote() {
+ async saveTodo() {
const { DateTime } = luxon;
const urlRegExp = /http(s)?:\/\/(www\.)?[a-zA-Z0-9@:%_+~#=~#?&/=\-;!.]{3,2000}/g;
- const note = this.noteInputRef.el.value.replace(urlRegExp, '$&').trim();
- if (!note) {
+ const todo = this.todoInputRef.el.value.replace(urlRegExp, '$&').trim();
+ if (!todo) {
return;
}
- await this.rpc("/note/new", {
- note: note,
- date_deadline: this.addingNoteDate ? this.addingNoteDate : DateTime.local(),
+ await this.rpc("/project_todo/new", {
+ todo_description: todo,
+ date_deadline: this.addingTodoDate ? this.addingTodoDate : DateTime.local(),
});
- this.state.addingNote = false;
- this.fetchSystrayActivities();
+ document.body.click(); // hack to close dropdown
+ this.notification.add(
+ this.env._t("Your to-do has been successfully added to your tasks and scheduled for completion."),
+ { type: "success" },
+ );
+ this.state.addingTodo = false;
},
});
diff --git a/addons/note/static/src/web/activity/activity_menu_patch.scss b/addons/todo/static/src/web/activity/activity_menu_patch.scss
similarity index 100%
rename from addons/note/static/src/web/activity/activity_menu_patch.scss
rename to addons/todo/static/src/web/activity/activity_menu_patch.scss
diff --git a/addons/note/static/src/web/activity/activity_menu_patch.xml b/addons/todo/static/src/web/activity/activity_menu_patch.xml
similarity index 64%
rename from addons/note/static/src/web/activity/activity_menu_patch.xml
rename to addons/todo/static/src/web/activity/activity_menu_patch.xml
index f23e8c63e94..339a444a284 100644
--- a/addons/note/static/src/web/activity/activity_menu_patch.xml
+++ b/addons/todo/static/src/web/activity/activity_menu_patch.xml
@@ -2,26 +2,26 @@
-
-
+
+
-
-
+
+
- Add a note
+ Add a To-do
-
+
-
+
diff --git a/addons/note/static/tests/activity_menu_tests.js b/addons/todo/static/tests/activity_menu_tests.js
similarity index 63%
rename from addons/note/static/tests/activity_menu_tests.js
rename to addons/todo/static/tests/activity_menu_tests.js
index 7f76c4d67fe..cf0ef07b5bf 100644
--- a/addons/note/static/tests/activity_menu_tests.js
+++ b/addons/todo/static/tests/activity_menu_tests.js
@@ -5,13 +5,13 @@ import { getFixture, editInput, patchDate } from "@web/../tests/helpers/utils";
let target;
-QUnit.module("note activity menu", {
+QUnit.module("todo activity menu", {
async beforeEach() {
target = getFixture();
},
});
-QUnit.test("create note from activity menu without date", async function (assert) {
+QUnit.test("create todo from activity menu without date", async function (assert) {
await start();
assert.containsOnce(target, ".o_menu_systray i[aria-label='Activities']");
assert.containsNone(target, ".o-mail-ActivityMenu-counter");
@@ -20,28 +20,34 @@ QUnit.test("create note from activity menu without date", async function (assert
target,
".o-mail-ActivityMenu:contains(Congratulations, you're done with your activities.)"
);
- assert.containsOnce(target, "button:contains(Add new note)");
- await click("button:contains(Add new note)");
- assert.containsNone(target, "button:contains(Add new note)");
+ assert.containsOnce(target, "button:contains(Add a To-do)");
+ await click("button:contains(Add a To-do)");
+ assert.containsNone(target, "button:contains(Add a To-do)");
assert.containsOnce(target, ".o-mail-ActivityMenu-show");
- await editInput(target, "input.o-mail-ActivityMenu-input", "New Note");
+ await editInput(target, "input.o-mail-ActivityMenu-input", "New To-do");
await click("button:contains(SAVE)");
+ // Need to reopen systray as it automatically closes when a todo is created
+ assert.containsOnce(target, ".o_menu_systray i[aria-label='Activities']");
+ await click(".o_menu_systray i[aria-label='Activities']");
assert.containsOnce(target, ".o-mail-ActivityMenu-counter:contains(1)");
assert.containsOnce(target, ".o-mail-ActivityGroup");
- assert.containsOnce(target, ".o-mail-ActivityGroup:contains(note.note)");
+ assert.containsOnce(target, ".o-mail-ActivityGroup:contains(project.task)");
assert.containsOnce(target, "button:contains(1 Today)");
- assert.containsOnce(target, "button:contains(Add new note)");
+ assert.containsOnce(target, "button:contains(Add a To-do)");
assert.containsNone(target, ".o-mail-ActivityMenu-show");
assert.containsNone(target, ".o-mail-ActivityMenu-input");
- await click("button:contains(Add new note)");
- assert.containsNone(target, "button:contains(Add new note)");
- await editInput(target, "input.o-mail-ActivityMenu-input", "New Note");
+ await click("button:contains(Add a To-do)");
+ assert.containsNone(target, "button:contains(Add a To-do)");
+ await editInput(target, "input.o-mail-ActivityMenu-input", "New To-do");
await click("button:contains(SAVE)");
+ // Need to reopen systray as it automatically closes when a todo is created
+ assert.containsOnce(target, ".o_menu_systray i[aria-label='Activities']");
+ await click(".o_menu_systray i[aria-label='Activities']");
assert.containsOnce(target, ".o-mail-ActivityMenu-counter:contains(2)");
assert.containsOnce(target, "button:contains(2 Today)");
});
-QUnit.test("create note from activity menu with date", async function (assert) {
+QUnit.test("create to-do from activity menu with date", async function (assert) {
patchDate(2023, 1, 1, 6, 0, 0);
const { DateTime } = luxon;
const today = DateTime.utc();
@@ -51,16 +57,19 @@ QUnit.test("create note from activity menu with date", async function (assert) {
assert.containsNone(target, ".o-mail-ActivityMenu-counter");
await click(".o_menu_systray i[aria-label='Activities']");
- await click("button:contains(Add new note)");
- await editInput(target, "input.o-mail-ActivityMenu-input", "New Note");
+ await click("button:contains(Add a To-do)");
+ await editInput(target, "input.o-mail-ActivityMenu-input", "New To-do");
await editInput(
target,
"input.o_datetime_input",
futureDay.toString(luxon.DateTime.DATE_SHORT)
);
await click("button:contains(SAVE)");
+ // Need to reopen systray as it automatically closes when a todo is created
+ assert.containsOnce(target, ".o_menu_systray i[aria-label='Activities']");
+ await click(".o_menu_systray i[aria-label='Activities']");
assert.containsOnce(target, ".o-mail-ActivityMenu-counter:contains(1)");
assert.containsOnce(target, ".o-mail-ActivityGroup");
- assert.containsOnce(target, ".o-mail-ActivityGroup:contains(note.note)");
+ assert.containsOnce(target, ".o-mail-ActivityGroup:contains(project.task)");
assert.containsOnce(target, "button:contains(1 Future)");
});
diff --git a/addons/note/static/tests/helpers/mock_server/controller/note.js b/addons/todo/static/tests/helpers/mock_server/controller/project_todo.js
similarity index 66%
rename from addons/note/static/tests/helpers/mock_server/controller/note.js
rename to addons/todo/static/tests/helpers/mock_server/controller/project_todo.js
index 2e92a160a9b..8d622de50c6 100644
--- a/addons/note/static/tests/helpers/mock_server/controller/note.js
+++ b/addons/todo/static/tests/helpers/mock_server/controller/project_todo.js
@@ -5,7 +5,7 @@ import { MockServer } from '@web/../tests/helpers/mock_server';
import { date_to_str } from 'web.time';
-patch(MockServer.prototype, 'note/controller/note', {
+patch(MockServer.prototype, 'project_todo/controller/project_todo', {
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -14,8 +14,8 @@ patch(MockServer.prototype, 'note/controller/note', {
* @override
*/
async _performRPC(route, args) {
- if (route === '/note/new') {
- return this._mockRouteNoteNew(args);
+ if (route === '/project_todo/new') {
+ return this._mockRouteTodoNew(args);
}
return this._super(...arguments);
},
@@ -25,17 +25,18 @@ patch(MockServer.prototype, 'note/controller/note', {
//--------------------------------------------------------------------------
/**
- * Simulates the `/note/new` route.
+ * Simulates the `/project_todo/new` route.
*
* @private
*/
- _mockRouteNoteNew(values) {
- const noteId = this.pyEnv['note.note'].create({ memo: values['note'] });
+ _mockRouteTodoNew(values) {
+ const taskId = this.pyEnv['project.task'].create({ description: values['todo_description'] });
if (values['date_deadline']) {
this.pyEnv['mail.activity'].create({
date_deadline: date_to_str(new Date(values['date_deadline'])),
- note_id: noteId,
- res_model: 'note.note',
+ note: values['todo_description'],
+ res_model: 'project.task',
+ res_id: taskId,
});
}
},
diff --git a/addons/note/static/tests/helpers/model_definitions_setup.js b/addons/todo/static/tests/helpers/model_definitions_setup.js
similarity index 73%
rename from addons/note/static/tests/helpers/model_definitions_setup.js
rename to addons/todo/static/tests/helpers/model_definitions_setup.js
index 9411e568f60..d0fb9c5300a 100644
--- a/addons/note/static/tests/helpers/model_definitions_setup.js
+++ b/addons/todo/static/tests/helpers/model_definitions_setup.js
@@ -2,4 +2,4 @@
import { addModelNamesToFetch } from '@bus/../tests/helpers/model_definitions_helpers';
-addModelNamesToFetch(['note.note']);
+addModelNamesToFetch(['project.task']);
diff --git a/addons/todo/tests/__init__.py b/addons/todo/tests/__init__.py
new file mode 100644
index 00000000000..af79c315e2d
--- /dev/null
+++ b/addons/todo/tests/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import test_access_rights
diff --git a/addons/todo/tests/test_access_rights.py b/addons/todo/tests/test_access_rights.py
new file mode 100644
index 00000000000..81f1a8d4ca9
--- /dev/null
+++ b/addons/todo/tests/test_access_rights.py
@@ -0,0 +1,20 @@
+from odoo.tests.common import users
+from odoo.exceptions import AccessError
+from odoo.addons.project.tests.test_access_rights import TestAccessRights
+
+class TestAccessRightsTodo(TestAccessRights):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.private_task = cls.env['project.task'].create({'name': 'OdooBot Private Task'})
+
+ @users('Internal user')
+ def test_internal_cannot_rud_private_task(self):
+ with self.assertRaises(AccessError):
+ self.private_task.with_user(self.env.user).write({'name': 'Test write'})
+
+ with self.assertRaises(AccessError):
+ self.private_task.with_user(self.env.user).unlink()
+
+ with self.assertRaises(AccessError):
+ self.private_task.with_user(self.env.user).read(['name'])
diff --git a/addons/todo/views/project_task_views.xml b/addons/todo/views/project_task_views.xml
new file mode 100644
index 00000000000..b562fe527df
--- /dev/null
+++ b/addons/todo/views/project_task_views.xml
@@ -0,0 +1,244 @@
+
+
+
+
+
+ project.task.kanban
+ project.task
+ 800
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Delete
+
+ Keep your work organized by using memos and to-do lists.
+ Your to-do items are private by default, but you can choose to share them with others by adding them as assignees.
+