Project: portal users: portal and following
diff --git a/addons/project/static/src/js/project_kanban.js b/addons/project/static/src/js/project_kanban.js
index 312b1d3c452..0b2ec385ff7 100644
--- a/addons/project/static/src/js/project_kanban.js
+++ b/addons/project/static/src/js/project_kanban.js
@@ -4,9 +4,11 @@ import KanbanController from 'web.KanbanController';
import KanbanRenderer from 'web.KanbanRenderer';
import KanbanView from 'web.KanbanView';
import KanbanColumn from 'web.KanbanColumn';
-import viewRegistry from 'web.view_registry';
import KanbanRecord from 'web.KanbanRecord';
+import KanbanModel from 'web.KanbanModel';
+import viewRegistry from 'web.view_registry';
import { ProjectControlPanel } from '@project/js/project_control_panel';
+import viewUtils from 'web.viewUtils';
// PROJECTS
@@ -70,6 +72,30 @@ const ProjectTaskKanbanRenderer = KanbanRenderer.extend({
config: Object.assign({}, KanbanRenderer.prototype.config, {
KanbanColumn: ProjectTaskKanbanColumn,
}),
+
+ /**
+ * Allows record drag when grouping by `personal_stage_type_ids`
+ *
+ * @override
+ */
+ _setState() {
+ this._super(...arguments);
+ const groupedBy = this.state.groupedBy[0];
+ const groupByFieldName = viewUtils.getGroupByField(groupedBy);
+ const field = this.state.fields[groupByFieldName] || {};
+ const fieldInfo = this.state.fieldsInfo.kanban[groupByFieldName] || {};
+
+ const grouped_by_date = ["date", "datetime"].includes(field.type);
+ const grouped_by_m2m = field.type === "many2many";
+ const readonly = !!field.readonly || !!fieldInfo.readonly;
+
+ const draggable = !readonly && (!grouped_by_m2m || groupByFieldName == 'personal_stage_type_ids') &&
+ (!grouped_by_date || fieldInfo.allowGroupRangeValue);
+
+ Object.assign(this.columnOptions, {
+ draggable,
+ });
+ }
});
export const ProjectKanbanController = KanbanController.extend({
@@ -93,8 +119,81 @@ export const ProjectKanbanController = KanbanController.extend({
},
});
+const ProjectTaskKanbanModel = KanbanModel.extend({
+
+ /**
+ * Upon updating `personal_stage_type_ids` we actually want to update the `personal_stage_type_id` field.
+ *
+ * @override
+ * @private
+ */
+ moveRecord: function (recordID, groupID, parentID) {
+ var self = this;
+ var parent = this.localData[parentID];
+ var new_group = this.localData[groupID];
+ var changes = {};
+ var groupedFieldName = viewUtils.getGroupByField(parent.groupedBy[0]);
+ var groupedField = parent.fields[groupedFieldName];
+ // for a date/datetime field, we take the last moment of the group as the group value
+ if (['date', 'datetime'].includes(groupedField.type)) {
+ changes[groupedFieldName] = viewUtils.getGroupValue(new_group, groupedFieldName);
+ } else if (groupedField.type === 'many2one') {
+ changes[groupedFieldName] = {
+ id: new_group.res_id,
+ display_name: new_group.value,
+ };
+ } else if (groupedField.type === 'selection') {
+ var value = _.findWhere(groupedField.selection, {1: new_group.value});
+ changes[groupedFieldName] = value && value[0] || false;
+ } else if (groupedField.type == 'many2many' && groupedFieldName == 'personal_stage_type_ids') {
+ changes['personal_stage_type_id'] = {
+ id: new_group.res_id,
+ display_name: new_group.value,
+ }
+ } else {
+ changes[groupedFieldName] = new_group.value;
+ }
+
+ // Manually updates groups data. Note: this is done before the actual
+ // save as it might need to perform a read group in some cases so those
+ // updated data might be overridden again.
+ var record = self.localData[recordID];
+ var resID = record.res_id;
+ // Remove record from its current group
+ var old_group;
+ for (var i = 0; i < parent.data.length; i++) {
+ old_group = self.localData[parent.data[i]];
+ var index = _.indexOf(old_group.data, recordID);
+ if (index >= 0) {
+ old_group.data.splice(index, 1);
+ old_group.count--;
+ if (!old_group.activeFilter || old_group.activeFilter.value === record.data[parent.progressBar.field]) {
+ // Here, the record leaving the old group matches its domain,
+ // so we must decrease the domainCount too.
+ old_group.domainCount--;
+ }
+ old_group.res_ids = _.without(old_group.res_ids, resID);
+ self._updateParentResIDs(old_group);
+ break;
+ }
+ }
+ // Add record to its new group
+ new_group.data.push(recordID);
+ new_group.res_ids.push(resID);
+ new_group.count++;
+
+ return this.notifyChanges(recordID, changes).then(function () {
+ return self.save(recordID);
+ }).then(function () {
+ record.parentID = new_group.id;
+ return [old_group.id, new_group.id];
+ });
+ },
+})
+
const ProjectKanbanView = KanbanView.extend({
config: _.extend({}, KanbanView.prototype.config, {
+ Model: ProjectTaskKanbanModel,
Controller: ProjectKanbanController,
Renderer: ProjectTaskKanbanRenderer,
ControlPanel: ProjectControlPanel,
diff --git a/addons/project/static/src/scss/project_form.scss b/addons/project/static/src/scss/project_form.scss
index a00a230a58e..4426d802fa9 100644
--- a/addons/project/static/src/scss/project_form.scss
+++ b/addons/project/static/src/scss/project_form.scss
@@ -34,3 +34,8 @@
color: $o-main-text-color;
}
}
+
+.o_form_project_tasks .o_project_task_project_field input::placeholder {
+ color: darkred;
+ font-style: italic;
+}
diff --git a/addons/project/static/src/scss/project_widgets.scss b/addons/project/static/src/scss/project_widgets.scss
index 3271bb3e9d3..db6e353e636 100644
--- a/addons/project/static/src/scss/project_widgets.scss
+++ b/addons/project/static/src/scss/project_widgets.scss
@@ -62,3 +62,22 @@
}
}
}
+
+.o_kanban_project_tasks .o_field_many2manytags, .o_kanban_tags{
+ margin: 0px;
+}
+
+.o_project_m2m_avatar {
+ width: 20px;
+ height: 20px;
+ margin-right: -1px;
+ display: inline-block;
+
+ img {
+ border-radius: 50%;
+ width: 20px;
+ height: 20px;
+ object-fit: cover;
+ margin-right: 4px;
+ }
+}
diff --git a/addons/project/tests/__init__.py b/addons/project/tests/__init__.py
index ee0446182b4..b79de62503e 100644
--- a/addons/project/tests/__init__.py
+++ b/addons/project/tests/__init__.py
@@ -14,4 +14,5 @@ from . import test_project_update_flow
from . import test_project_update_ui
from . import test_portal
from . import test_multicompany
+from . import test_personal_stages
from . import test_task_dependencies
diff --git a/addons/project/tests/test_access_rights.py b/addons/project/tests/test_access_rights.py
index 0026c15d5a3..b9ab34ded48 100644
--- a/addons/project/tests/test_access_rights.py
+++ b/addons/project/tests/test_access_rights.py
@@ -235,13 +235,13 @@ class TestProjectPortalCommon(TestProjectCommon):
'groups_id': [(6, 0, [])]})
self.task_3 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
- 'name': 'Test3', 'user_id': self.user_portal.id, 'project_id': self.project_pigs.id})
+ 'name': 'Test3', 'user_ids': self.user_portal, 'project_id': self.project_pigs.id})
self.task_4 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
- 'name': 'Test4', 'user_id': self.user_public.id, 'project_id': self.project_pigs.id})
+ 'name': 'Test4', 'user_ids': self.user_public, 'project_id': self.project_pigs.id})
self.task_5 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
- 'name': 'Test5', 'user_id': False, 'project_id': self.project_pigs.id})
+ 'name': 'Test5', 'user_ids': False, 'project_id': self.project_pigs.id})
self.task_6 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
- 'name': 'Test5', 'user_id': False, 'project_id': self.project_pigs.id})
+ 'name': 'Test5', 'user_ids': False, 'project_id': self.project_pigs.id})
class TestPortalProject(TestProjectPortalCommon):
diff --git a/addons/project/tests/test_multicompany.py b/addons/project/tests/test_multicompany.py
index b68d26c4110..85703c442e7 100644
--- a/addons/project/tests/test_multicompany.py
+++ b/addons/project/tests/test_multicompany.py
@@ -179,12 +179,12 @@ class TestMultiCompanyProject(TestMultiCompanyCommon):
Task = cls.env['project.task'].with_context({'mail_create_nolog': True, 'tracking_disable': True})
cls.task_1 = Task.create({
'name': 'Task 1 in Project A',
- 'user_id': cls.user_employee_company_a.id,
+ 'user_ids': cls.user_employee_company_a,
'project_id': cls.project_company_a.id
})
cls.task_2 = Task.create({
'name': 'Task 2 in Project B',
- 'user_id': cls.user_employee_company_b.id,
+ 'user_ids': cls.user_employee_company_b,
'project_id': cls.project_company_b.id
})
diff --git a/addons/project/tests/test_personal_stages.py b/addons/project/tests/test_personal_stages.py
new file mode 100644
index 00000000000..3fc3f59b8a9
--- /dev/null
+++ b/addons/project/tests/test_personal_stages.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+from odoo.tests import tagged
+
+from .test_project_base import TestProjectCommon
+
+@tagged('-at_install', 'post_install', 'personal_stages')
+class TestPersonalStages(TestProjectCommon):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.user_stages = cls.env['project.task.type'].search([('user_id', '=', cls.user_projectuser.id)])
+ cls.manager_stages = cls.env['project.task.type'].search([('user_id', '=', cls.user_projectmanager.id)])
+
+ def test_personal_stage_base(self):
+ # Project User is assigned to task_1 he should be able to see a personal stage
+ self.task_1.with_user(self.user_projectuser)._compute_personal_stage_id()
+ self.assertTrue(self.task_1.with_user(self.user_projectuser).personal_stage_type_id,
+ 'Project User is assigned to task 1, he should have a personal stage assigned.')
+
+ self.task_1.with_user(self.user_projectmanager)._compute_personal_stage_id()
+ self.assertFalse(self.env['project.task'].browse(self.task_1.id).with_user(self.user_projectmanager).personal_stage_type_id,
+ 'Project Manager is not assigned to task 1, he should not have a personal stage assigned.')
+
+ # Now assign a second user to our task_1
+ self.task_1.user_ids += self.user_projectmanager
+ self.assertTrue(self.task_1.with_user(self.user_projectmanager).personal_stage_type_id,
+ 'Project Manager has now been assigned to task 1 and should have a personal stage assigned.')
+
+ self.task_1.with_user(self.user_projectmanager)._compute_personal_stage_id()
+ task_1_manager_stage = self.task_1.with_user(self.user_projectmanager).personal_stage_type_id
+
+ self.task_1.with_user(self.user_projectuser)._compute_personal_stage_id()
+ self.task_1.with_user(self.user_projectuser).personal_stage_type_id = self.user_stages[1]
+ self.assertEqual(self.task_1.with_user(self.user_projectuser).personal_stage_type_id, self.user_stages[1],
+ 'Assigning another personal stage to the task should have changed it for user 1.')
+
+ self.task_1.with_user(self.user_projectmanager)._compute_personal_stage_id()
+ self.assertEqual(self.task_1.with_user(self.user_projectmanager).personal_stage_type_id, task_1_manager_stage,
+ 'Modifying the personal stage of Project User should not have affected the personal stage of Project Manager.')
+
+ self.task_2.with_user(self.user_projectmanager).personal_stage_type_id = self.manager_stages[1]
+ self.assertEqual(self.task_1.with_user(self.user_projectmanager).personal_stage_type_id, task_1_manager_stage,
+ 'Modifying the personal stage on task 2 for Project Manager should not have affected the stage on task 1.')
+
+ def test_personal_stage_search(self):
+ self.task_2.user_ids += self.user_projectuser
+ # Make sure both personal stages are different
+ self.task_1.with_user(self.user_projectuser).personal_stage_type_id = self.user_stages[0]
+ self.task_2.with_user(self.user_projectuser).personal_stage_type_id = self.user_stages[1]
+ tasks = self.env['project.task'].with_user(self.user_projectuser).search([('personal_stage_type_id', '=', self.user_stages[0].id)])
+ self.assertTrue(tasks, 'The search result should not be empty.')
+ for task in tasks:
+ self.assertEqual(task.personal_stage_type_id, self.user_stages[0],
+ 'The search should only have returned task that are in the inbox personal stage.')
+
+ def test_personal_stage_read_group(self):
+ self.task_1.user_ids += self.user_projectmanager
+ self.task_1.with_user(self.user_projectmanager).personal_stage_type_id = self.manager_stages[1]
+ #Makes sure the personal stage for project manager is saved in the database
+ self.env['project.task'].flush()
+ read_group_user = self.env['project.task'].with_user(self.user_projectuser).read_group(
+ [('user_ids', '=', self.user_projectuser.id)], fields=['sequence:avg'], groupby=['personal_stage_type_ids'])
+ # Check that the result is at least a bit coherent
+ self.assertEqual(len(self.user_stages), len(read_group_user),
+ 'read_group should return %d groups' % len(self.user_stages))
+ # User has only one task assigned the sum of all counts should be 1
+ total = 0
+ for group in read_group_user:
+ total += group['personal_stage_type_ids_count']
+ self.assertEqual(1, total,
+ 'read_group should not have returned more tasks than the user is assigned to.')
+ read_group_manager = self.env['project.task'].with_user(self.user_projectmanager).read_group(
+ [('user_ids', '=', self.user_projectmanager.id)], fields=['sequence:avg'], groupby=['personal_stage_type_ids'])
+ self.assertEqual(len(self.manager_stages), len(read_group_manager),
+ 'read_group should return %d groups' % len(self.user_stages))
+ total = 0
+ total_stage_0 = 0
+ total_stage_1 = 0
+ for group in read_group_manager:
+ total += group['personal_stage_type_ids_count']
+ # Check that we have a task in both stages
+ if group['personal_stage_type_ids'][0] == self.manager_stages[0].id:
+ total_stage_0 += 1
+ elif group['personal_stage_type_ids'][0] == self.manager_stages[1].id:
+ total_stage_1 += 1
+ self.assertEqual(2, total,
+ 'read_group should not have returned more tasks than the user is assigned to.')
+ self.assertEqual(1, total_stage_0)
+ self.assertEqual(1, total_stage_1)
diff --git a/addons/project/tests/test_project_base.py b/addons/project/tests/test_project_base.py
index 0c62144e98a..8ba3c66640d 100644
--- a/addons/project/tests/test_project_base.py
+++ b/addons/project/tests/test_project_base.py
@@ -60,11 +60,11 @@ class TestProjectCommon(TransactionCase):
# Already-existing tasks in Pigs
cls.task_1 = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
'name': 'Pigs UserTask',
- 'user_id': cls.user_projectuser.id,
+ 'user_ids': cls.user_projectuser,
'project_id': cls.project_pigs.id})
cls.task_2 = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
'name': 'Pigs ManagerTask',
- 'user_id': cls.user_projectmanager.id,
+ 'user_ids': cls.user_projectmanager,
'project_id': cls.project_pigs.id})
# Test 'Goats' project, same as 'Pigs', but with 2 stages
diff --git a/addons/project/tests/test_project_flow.py b/addons/project/tests/test_project_flow.py
index 4fa67397c0e..488809e08a2 100644
--- a/addons/project/tests/test_project_flow.py
+++ b/addons/project/tests/test_project_flow.py
@@ -112,7 +112,7 @@ class TestProjectFlow(TestProjectCommon):
parent_task = Task.create({
'name': 'Mother Task',
- 'user_id': self.user_projectuser.id,
+ 'user_ids': self.user_projectuser,
'project_id': self.project_pigs.id,
'partner_id': self.partner_2.id,
'planned_hours': 12,
@@ -120,7 +120,7 @@ class TestProjectFlow(TestProjectCommon):
another_parent_task = Task.create({
'name': 'Another Mother Task',
- 'user_id': self.user_projectuser.id,
+ 'user_ids': self.user_projectuser,
'project_id': self.project_pigs.id,
'partner_id': self.partner_3.id,
'planned_hours': 0,
@@ -210,7 +210,7 @@ class TestProjectFlow(TestProjectCommon):
Task = self.env['project.task'].with_context({'tracking_disable': True})
first_task = Task.create({
'name': 'first task',
- 'user_id': self.user_projectuser.id,
+ 'user_ids': self.user_projectuser,
'project_id': self.project_pigs.id,
'partner_id': self.partner_2.id,
})
diff --git a/addons/project/tests/test_project_sharing.py b/addons/project/tests/test_project_sharing.py
index 04af79e5333..ff3ff4f948b 100644
--- a/addons/project/tests/test_project_sharing.py
+++ b/addons/project/tests/test_project_sharing.py
@@ -42,12 +42,12 @@ class TestProjectSharingCommon(TestProjectCommon):
cls.task_cow = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
'name': 'Cow UserTask',
- 'user_id': cls.user_projectuser.id,
+ 'user_ids': cls.user_projectuser,
'project_id': cls.project_cows.id,
})
cls.task_portal = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
'name': 'Portal UserTask',
- 'user_id': cls.user_projectuser.id,
+ 'user_ids': cls.user_projectuser,
'project_id': cls.project_portal.id,
})
@@ -123,7 +123,7 @@ class TestProjectSharing(TestProjectSharingCommon):
task = form.save()
self.assertEqual(task.name, 'Test')
self.assertEqual(task.project_id, self.project_portal)
- self.assertEqual(task.user_id, self.user_portal)
+ self.assertEqual(task.user_ids, self.user_portal)
# 3.1) Try to change the project of the new task with this user.
with self.assertRaises(AssertionError, msg="Should not accept the portal user changes the project of the task."):
form.project_id = self.project_cows
@@ -172,4 +172,4 @@ class TestProjectSharing(TestProjectSharingCommon):
form.save()
self.assertEqual(task.child_ids.name, 'Test Subtask')
self.assertEqual(task.child_ids.project_id, self.project_cows)
- self.assertEqual(task.child_ids.user_id, self.user_portal)
+ self.assertEqual(task.child_ids.user_ids, self.user_portal)
diff --git a/addons/project/tests/test_project_subtasks.py b/addons/project/tests/test_project_subtasks.py
index ff6be35a93f..d2de2489f82 100644
--- a/addons/project/tests/test_project_subtasks.py
+++ b/addons/project/tests/test_project_subtasks.py
@@ -10,10 +10,6 @@ class TestProjectSubtasks(TestProjectCommon):
"""
Create a task in the default task form should take the project set in the form or the default project in the context
"""
- with self.assertRaises(AssertionError, msg="Should not accept a form without project. Project is required"):
- with Form(self.env['project.task'].with_context({'tracking_disable': True})) as task_form:
- task_form.name = 'Test Task 1'
-
with Form(self.env['project.task'].with_context({'tracking_disable': True})) as task_form:
task_form.name = 'Test Task 1'
task_form.project_id = self.project_pigs
@@ -33,10 +29,6 @@ class TestProjectSubtasks(TestProjectCommon):
"""
Create a task in the task form 2 should take the project set in the form or the default project in the context
"""
- with self.assertRaises(AssertionError, msg="Should not accept a form without project. Project is required"):
- with Form(self.env['project.task'].with_context({'tracking_disable': True}), view="project.view_task_form2") as task_form:
- task_form.name = 'Test Task 1'
-
with Form(self.env['project.task'].with_context({'tracking_disable': True}), view="project.view_task_form2") as task_form:
task_form.name = 'Test Task 1'
task_form.project_id = self.project_pigs
diff --git a/addons/project/tests/test_task_dependencies.py b/addons/project/tests/test_task_dependencies.py
index 5ddc6e640c3..af8feb89028 100644
--- a/addons/project/tests/test_task_dependencies.py
+++ b/addons/project/tests/test_task_dependencies.py
@@ -20,7 +20,7 @@ class TestTaskDependencies(TestProjectCommon):
})
cls.task_3 = cls.env['project.task'].with_context({'mail_create_nolog': True}).create({
'name': 'Pigs UserTask 2',
- 'user_id': cls.user_projectuser.id,
+ 'user_ids': cls.user_projectuser,
'project_id': cls.project_pigs.id,
})
@@ -97,7 +97,8 @@ class TestTaskDependencies(TestProjectCommon):
self.env['res.config.settings'].create({
'group_project_task_dependencies': True
}).execute()
- self.task_1.write({
+ # `depend_on_ids` is tracked
+ self.task_1.with_context(mail_notrack=True).write({
'depend_on_ids': [Command.link(self.task_2.id)]
})
self.cr.precommit.clear()
diff --git a/addons/project/views/project_portal_templates.xml b/addons/project/views/project_portal_templates.xml
index ff01397ecae..6e8d3f0dbcc 100644
--- a/addons/project/views/project_portal_templates.xml
+++ b/addons/project/views/project_portal_templates.xml
@@ -103,8 +103,8 @@
in stage:
|
- assigned to:
- |
+ assigned to:
+
for customer:
|
@@ -119,7 +119,7 @@
#
-
+
|
@@ -177,18 +177,20 @@
-
-
+
+
Assigned to
-
- ![Contact]()
-
-
+
+
+ ![Contact]()
+
+
+
diff --git a/addons/project/views/project_sharing_views.xml b/addons/project/views/project_sharing_views.xml
index ea603592e99..125ee028442 100644
--- a/addons/project/views/project_sharing_views.xml
+++ b/addons/project/views/project_sharing_views.xml
@@ -31,7 +31,7 @@
-
+
@@ -90,7 +90,7 @@
-
+
@@ -116,7 +116,7 @@
-
+
@@ -151,10 +151,10 @@
-
@@ -173,7 +173,7 @@
-
+
@@ -182,7 +182,7 @@
-
+
@@ -208,11 +208,11 @@
-
+
-
+
@@ -223,7 +223,7 @@
domain="[('activity_ids.date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/>
-
+
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index 7d74d523fc6..b48ab46a6cb 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -18,14 +18,13 @@
-
+
-
-
-
+
+
@@ -46,7 +45,8 @@
domain="[('my_activity_date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/>
-
+
+
@@ -78,7 +78,7 @@
kanban,tree,form,calendar,pivot,graph,activity
[('display_project_id', '=', active_id)]
{
- 'pivot_row_groupby': ['user_id'],
+ 'pivot_row_groupby': ['user_ids'],
'default_project_id': active_id,
'show_project_update': True,
}
@@ -184,6 +184,7 @@
+
@@ -783,7 +784,7 @@
+ attrs="{'invisible' : [('user_ids', '!=', False)]}" data-hotkey="q"/>
@@ -816,12 +817,18 @@
-
+
+
+ Private
+
+
+
-
-
+
@@ -854,7 +861,7 @@
-
+
@@ -871,7 +878,7 @@
-
+
@@ -980,7 +987,8 @@
-
+
@@ -1098,7 +1114,7 @@
-
+
@@ -1131,10 +1147,10 @@
project.task
-
-
-
+
+
@@ -1181,10 +1197,10 @@
project.task
-
+
-
+
@@ -1213,10 +1229,10 @@
- Tasks
+ My Tasks
project.task
- tree,kanban,form,calendar,pivot,graph,activity
- {'search_default_my_tasks': 1, 'all_task': 0}
+ kanban,tree,form,calendar,pivot,graph,activity
+ {'search_default_my_tasks': 1, 'search_default_personal_stage': 1, 'all_task': 0}
@@ -1239,7 +1255,7 @@
-
@@ -1283,7 +1299,7 @@
Assigned Tasks
project.task
tree,form,calendar,graph
- {'search_default_user_id': [active_id], 'default_user_id': active_id}
+ {'search_default_user_ids': [active_id], 'default_user_ids': active_id}
[('display_project_id', '!=', False)]
form
diff --git a/addons/sale_project/models/sale_order.py b/addons/sale_project/models/sale_order.py
index 32ec48d5aba..b91be8db811 100644
--- a/addons/sale_project/models/sale_order.py
+++ b/addons/sale_project/models/sale_order.py
@@ -259,7 +259,7 @@ class SaleOrderLine(models.Model):
'sale_line_id': self.id,
'sale_order_id': self.order_id.id,
'company_id': project.company_id.id,
- 'user_id': False, # force non assigned task, as created as sudo()
+ 'user_ids': False, # force non assigned task, as created as sudo()
}
def _timesheet_create_task(self, project):
diff --git a/addons/sale_project/tests/test_child_tasks.py b/addons/sale_project/tests/test_child_tasks.py
index 03d9e1dcde6..73a91a47be2 100644
--- a/addons/sale_project/tests/test_child_tasks.py
+++ b/addons/sale_project/tests/test_child_tasks.py
@@ -34,9 +34,9 @@ class TestNestedTaskUpdate(TransactionCase):
#----------------------------------
def test_creating_subtask_user_id_on_parent_dont_go_on_child(self):
- parent = self.env['project.task'].create({'name': 'parent', 'user_id': self.user.id})
- child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id, 'user_id': False})
- self.assertFalse(child.user_id)
+ parent = self.env['project.task'].create({'name': 'parent', 'user_ids': [(4, self.user.id)]})
+ child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id, 'user_ids': False})
+ self.assertFalse(child.user_ids)
def test_creating_subtask_partner_id_on_parent_goes_on_child(self):
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.user.partner_id.id})
@@ -88,13 +88,13 @@ class TestNestedTaskUpdate(TransactionCase):
#----------------------------------------
def test_write_user_id_on_parent_dont_write_on_child(self):
- parent = self.env['project.task'].create({'name': 'parent', 'user_id': False})
- child = self.env['project.task'].create({'name': 'child', 'user_id': False, 'parent_id': parent.id})
- self.assertFalse(child.user_id)
- parent.write({'user_id': self.user.id})
- self.assertFalse(child.user_id)
- parent.write({'user_id': False})
- self.assertFalse(child.user_id)
+ parent = self.env['project.task'].create({'name': 'parent', 'user_ids': False})
+ child = self.env['project.task'].create({'name': 'child', 'user_ids': False, 'parent_id': parent.id})
+ self.assertFalse(child.user_ids)
+ parent.write({'user_ids': [(4, self.user.id)]})
+ self.assertFalse(child.user_ids)
+ parent.write({'user_ids': False})
+ self.assertFalse(child.user_ids)
def test_write_partner_id_on_parent_write_on_child(self):
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': False})
@@ -148,11 +148,11 @@ class TestNestedTaskUpdate(TransactionCase):
#----------------------------------
def test_linking_user_id_on_parent_dont_write_on_child(self):
- parent = self.env['project.task'].create({'name': 'parent', 'user_id': self.user.id})
- child = self.env['project.task'].create({'name': 'child', 'user_id': False})
- self.assertFalse(child.user_id)
+ parent = self.env['project.task'].create({'name': 'parent', 'user_ids': [(4, self.user.id)]})
+ child = self.env['project.task'].create({'name': 'child', 'user_ids': False})
+ self.assertFalse(child.user_ids)
child.write({'parent_id': parent.id})
- self.assertFalse(child.user_id)
+ self.assertFalse(child.user_ids)
def test_linking_partner_id_on_parent_write_on_child(self):
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.user.partner_id.id})
@@ -195,8 +195,8 @@ class TestNestedTaskUpdate(TransactionCase):
self.assertFalse(child.sale_line_id)
def test_writing_on_parent_with_multiple_tasks(self):
- parent = self.env['project.task'].create({'name': 'parent', 'user_id': False, 'partner_id': self.partner.id})
- children_values = [{'name': 'child%s' % i, 'user_id': False, 'parent_id': parent.id} for i in range(5)]
+ parent = self.env['project.task'].create({'name': 'parent', 'user_ids': False, 'partner_id': self.partner.id})
+ children_values = [{'name': 'child%s' % i, 'user_ids': False, 'parent_id': parent.id} for i in range(5)]
children = self.env['project.task'].create(children_values)
children._compute_partner_id()
# test writing sale_line_id
@@ -207,17 +207,17 @@ class TestNestedTaskUpdate(TransactionCase):
self.assertEqual(child.sale_line_id, self.order_line)
def test_linking_on_parent_with_multiple_tasks(self):
- parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.partner.id, 'sale_line_id': self.order_line.id, 'user_id': self.user.id})
- children_values = [{'name': 'child%s' % i, 'user_id': False} for i in range(5)]
+ parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.partner.id, 'sale_line_id': self.order_line.id, 'user_ids': [(4, self.user.id)]})
+ children_values = [{'name': 'child%s' % i, 'user_ids': False} for i in range(5)]
children = self.env['project.task'].create(children_values)
- # test writing user_id and sale_line_id
+ # test writing user_ids and sale_line_id
for child in children:
- self.assertFalse(child.user_id)
+ self.assertFalse(child.user_ids)
self.assertFalse(child.sale_line_id)
children.write({'parent_id': parent.id})
for child in children:
self.assertEqual(child.sale_line_id, self.order_line)
- self.assertFalse(child.user_id)
+ self.assertFalse(child.user_ids)
diff --git a/addons/sale_timesheet/data/sale_service_demo.xml b/addons/sale_timesheet/data/sale_service_demo.xml
index 498bf7feae1..e1339f27893 100644
--- a/addons/sale_timesheet/data/sale_service_demo.xml
+++ b/addons/sale_timesheet/data/sale_service_demo.xml
@@ -29,7 +29,7 @@
Internal training
-
+
@@ -167,7 +167,7 @@
-
+
@@ -482,7 +482,7 @@
-
+
diff --git a/addons/sale_timesheet/tests/test_sale_service.py b/addons/sale_timesheet/tests/test_sale_service.py
index 53d54c0b40b..f3c85bcacb4 100644
--- a/addons/sale_timesheet/tests/test_sale_service.py
+++ b/addons/sale_timesheet/tests/test_sale_service.py
@@ -246,7 +246,7 @@ class TestSaleService(TestCommonSaleTimesheet):
self.assertTrue(so_line1.project_id, "SO confirmation should create a project and link it to SOL")
self.assertEqual(self.sale_order.tasks_count, 1, "The SO should have only one task")
self.assertEqual(so_line1.task_id.sale_line_id, so_line1, "The created task is also linked to its origin sale line, for invoicing purpose.")
- self.assertFalse(so_line1.task_id.user_id, "The created task should be unassigned")
+ self.assertFalse(so_line1.task_id.user_ids, "The created task should be unassigned")
self.assertEqual(so_line1.product_uom_qty, so_line1.task_id.planned_hours, "The planned hours should be the same as the ordered quantity of the native SO line")
so_line1.write({'product_uom_qty': 20})
diff --git a/addons/sale_timesheet/views/project_task_views.xml b/addons/sale_timesheet/views/project_task_views.xml
index 0b2c8d0d3de..5221810b28a 100644
--- a/addons/sale_timesheet/views/project_task_views.xml
+++ b/addons/sale_timesheet/views/project_task_views.xml
@@ -130,7 +130,7 @@
timesheet_invoice_id != False
-
+
|