diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml
index 3114143c394..0e2e06c9d64 100644
--- a/addons/hr_timesheet/views/project_views.xml
+++ b/addons/hr_timesheet/views/project_views.xml
@@ -81,21 +81,6 @@
24
-
-
-
diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py
index dd1fdad5989..ed881247ff4 100644
--- a/addons/project/__manifest__.py
+++ b/addons/project/__manifest__.py
@@ -48,7 +48,6 @@
'data/mail_template_data.xml',
'data/project_data.xml',
'wizard/project_task_type_delete_views.xml',
- 'wizard/project_milestone_reach_wizard.xml',
],
'demo': [
'data/mail_template_demo.xml',
@@ -73,7 +72,6 @@
'project/static/src/js/project_rating_graph_view.js',
'project/static/src/js/project_rating_pivot_view.js',
'project/static/src/js/project_task_kanban_examples.js',
- 'project/static/src/js/subtasks_list_renderer.js',
'project/static/src/js/tours/project.js',
'project/static/src/js/project_calendar.js',
'project/static/src/js/widgets/*',
diff --git a/addons/project/controllers/portal.py b/addons/project/controllers/portal.py
index a4c3313dad7..33e0da030df 100644
--- a/addons/project/controllers/portal.py
+++ b/addons/project/controllers/portal.py
@@ -45,6 +45,10 @@ class ProjectCustomerPortal(CustomerPortal):
project=project,
task_url=f'projects/{project.id}/task',
)
+ # default value is set to 'project' in _prepare_tasks_values, so we have to set it to 'none' here.
+ if not groupby:
+ values['groupby'] = 'none'
+
return self._get_page_view_values(project, access_token, values, 'my_projects_history', False, **kwargs)
def _prepare_project_domain(self):
@@ -246,28 +250,33 @@ class ProjectCustomerPortal(CustomerPortal):
}
return self._get_page_view_values(task, access_token, values, history, False, **kwargs)
- def _task_get_searchbar_sortings(self):
- return {
+ def _task_get_searchbar_sortings(self, milestones_allowed):
+ values = {
'date': {'label': _('Newest'), 'order': 'create_date desc', 'sequence': 1},
'name': {'label': _('Title'), 'order': 'name', 'sequence': 2},
'project': {'label': _('Project'), 'order': 'project_id, stage_id', 'sequence': 3},
'users': {'label': _('Assignees'), 'order': 'user_ids', 'sequence': 4},
'stage': {'label': _('Stage'), 'order': 'stage_id, project_id', 'sequence': 5},
'status': {'label': _('Status'), 'order': 'kanban_state', 'sequence': 6},
- 'priority': {'label': _('Priority'), 'order': 'priority desc', 'sequence': 7},
- 'date_deadline': {'label': _('Deadline'), 'order': 'date_deadline asc', 'sequence': 8},
- 'update': {'label': _('Last Stage Update'), 'order': 'date_last_stage_update desc', 'sequence': 10},
+ 'priority': {'label': _('Priority'), 'order': 'priority desc', 'sequence': 8},
+ 'date_deadline': {'label': _('Deadline'), 'order': 'date_deadline asc', 'sequence': 9},
+ 'update': {'label': _('Last Stage Update'), 'order': 'date_last_stage_update desc', 'sequence': 11},
}
+ if milestones_allowed:
+ values['milestone'] = {'label': _('Milestone'), 'order': 'milestone_id', 'sequence': 7}
+ return values
- def _task_get_searchbar_groupby(self):
+ def _task_get_searchbar_groupby(self, milestones_allowed):
values = {
'none': {'input': 'none', 'label': _('None'), 'order': 1},
'project': {'input': 'project', 'label': _('Project'), 'order': 2},
'stage': {'input': 'stage', 'label': _('Stage'), 'order': 4},
'status': {'input': 'status', 'label': _('Status'), 'order': 5},
- 'priority': {'input': 'priority', 'label': _('Priority'), 'order': 6},
- 'customer': {'input': 'customer', 'label': _('Customer'), 'order': 9},
+ 'priority': {'input': 'priority', 'label': _('Priority'), 'order': 7},
+ 'customer': {'input': 'customer', 'label': _('Customer'), 'order': 10},
}
+ if milestones_allowed:
+ values['milestone'] = {'input': 'milestone', 'label': _('Milestone'), 'order': 6}
return dict(sorted(values.items(), key=lambda item: item[1]["order"]))
def _task_get_groupby_mapping(self):
@@ -275,6 +284,7 @@ class ProjectCustomerPortal(CustomerPortal):
'project': 'project_id',
'stage': 'stage_id',
'customer': 'partner_id',
+ 'milestone': 'milestone_id',
'priority': 'priority',
'status': 'kanban_state',
}
@@ -286,7 +296,7 @@ class ProjectCustomerPortal(CustomerPortal):
return order
return '%s, %s' % (field_name, order)
- def _task_get_searchbar_inputs(self):
+ def _task_get_searchbar_inputs(self, milestones_allowed):
values = {
'all': {'input': 'all', 'label': _('Search in All'), 'order': 1},
'content': {'input': 'content', 'label': Markup(_('Search (in Content)')), 'order': 1},
@@ -295,9 +305,12 @@ class ProjectCustomerPortal(CustomerPortal):
'users': {'input': 'users', 'label': _('Search in Assignees'), 'order': 3},
'stage': {'input': 'stage', 'label': _('Search in Stages'), 'order': 4},
'status': {'input': 'status', 'label': _('Search in Status'), 'order': 5},
- 'priority': {'input': 'priority', 'label': _('Search in Priority'), 'order': 6},
- 'message': {'input': 'message', 'label': _('Search in Messages'), 'order': 10},
+ 'priority': {'input': 'priority', 'label': _('Search in Priority'), 'order': 7},
+ 'message': {'input': 'message', 'label': _('Search in Messages'), 'order': 11},
}
+ if milestones_allowed:
+ values['milestone'] = {'input': 'milestone', 'label': _('Search in Milestone'), 'order': 6}
+
return dict(sorted(values.items(), key=lambda item: item[1]["order"]))
def _task_get_search_domain(self, search_in, search):
@@ -315,6 +328,8 @@ class ProjectCustomerPortal(CustomerPortal):
search_domain.append([('project_id', 'ilike', search)])
if search_in in ('ref', 'all'):
search_domain.append([('id', 'ilike', search)])
+ if search_in in ('milestone', 'all'):
+ search_domain.append([('milestone_id', 'ilike', search)])
if search_in in ('users', 'all'):
user_ids = request.env['res.users'].sudo().search([('name', 'ilike', search)])
search_domain.append([('user_ids', 'in', user_ids.ids)])
@@ -328,12 +343,15 @@ class ProjectCustomerPortal(CustomerPortal):
def _prepare_tasks_values(self, page, date_begin, date_end, sortby, search, search_in, groupby, url="/my/tasks", domain=None, su=False):
values = self._prepare_portal_layout_values()
- searchbar_sortings = dict(sorted(self._task_get_searchbar_sortings().items(),
- key=lambda item: item[1]["sequence"]))
- searchbar_inputs = self._task_get_searchbar_inputs()
- searchbar_groupby = self._task_get_searchbar_groupby()
Task = request.env['project.task']
+ milestone_domain = AND([domain, [('allow_milestones', '=', 'True')]])
+ milestones_allowed = Task.sudo().search_count(milestone_domain, limit=1) == 1
+ searchbar_sortings = dict(sorted(self._task_get_searchbar_sortings(milestones_allowed).items(),
+ key=lambda item: item[1]["sequence"]))
+ searchbar_inputs = self._task_get_searchbar_inputs(milestones_allowed)
+ searchbar_groupby = self._task_get_searchbar_groupby(milestones_allowed)
+
if not domain:
domain = []
if not su and Task.check_access_rights('read'):
@@ -341,17 +359,20 @@ class ProjectCustomerPortal(CustomerPortal):
Task = Task.sudo()
# default sort by value
- if not sortby:
+ if not sortby or (sortby == 'milestone' and not milestones_allowed):
sortby = 'date'
order = searchbar_sortings[sortby]['order']
# default group by value
- if not groupby:
+ if not groupby or (groupby == 'milestone' and not milestones_allowed):
groupby = 'project'
if date_begin and date_end:
domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
+ # search reset if needed
+ if not milestones_allowed and search_in == 'milestone':
+ search_in = 'all'
# search
if search and search_in:
domain += self._task_get_search_domain(search_in, search)
@@ -363,10 +384,25 @@ class ProjectCustomerPortal(CustomerPortal):
tasks = Task.search(domain, order=order, limit=self._items_per_page, offset=pager_offset)
request.session['my_project_tasks_history' if url.startswith('/my/projects') else 'my_tasks_history'] = tasks.ids[:100]
+ tasks_project_allow_milestone = tasks.filtered(lambda t: t.allow_milestones)
+ tasks_no_milestone = tasks - tasks_project_allow_milestone
+
groupby_mapping = self._task_get_groupby_mapping()
group = groupby_mapping.get(groupby)
if group:
- grouped_tasks = [Task.concat(*g) for k, g in groupbyelem(tasks, itemgetter(group))]
+ if group == 'milestone_id':
+ grouped_tasks = [Task.concat(*g) for k, g in groupbyelem(tasks_project_allow_milestone, itemgetter(group))]
+
+ if not grouped_tasks:
+ grouped_tasks = [tasks_no_milestone]
+ else:
+ if grouped_tasks[len(grouped_tasks) - 1][0].milestone_id and tasks_no_milestone:
+ grouped_tasks.append(tasks_no_milestone)
+ else:
+ grouped_tasks[len(grouped_tasks) - 1] |= tasks_no_milestone
+
+ else:
+ grouped_tasks = [Task.concat(*g) for k, g in groupbyelem(tasks, itemgetter(group))]
else:
grouped_tasks = [tasks]
@@ -382,6 +418,7 @@ class ProjectCustomerPortal(CustomerPortal):
'date': date_begin,
'date_end': date_end,
'grouped_tasks': get_grouped_tasks,
+ 'allow_milestone': milestones_allowed,
'page_name': 'task',
'default_url': url,
'task_url': 'tasks',
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 3ee25339c42..b50fc7ed2aa 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -778,21 +778,6 @@ class Project(models.Model):
action_context['search_default_project_id'] = self.id
return dict(action, context=action_context)
- def action_view_analytic_account_entries(self):
- self.ensure_one()
- return {
- 'res_model': 'account.analytic.line',
- 'type': 'ir.actions.act_window',
- 'name': _("Gross Margin"),
- 'domain': [('account_id', '=', self.analytic_account_id.id)],
- 'views': [(self.env.ref('analytic.view_account_analytic_line_tree').id, 'list'),
- (self.env.ref('analytic.view_account_analytic_line_form').id, 'form'),
- (self.env.ref('analytic.view_account_analytic_line_graph').id, 'graph'),
- (self.env.ref('analytic.view_account_analytic_line_pivot').id, 'pivot')],
- 'view_mode': 'tree,form,graph,pivot',
- 'context': {'search_default_group_date': 1, 'default_account_id': self.analytic_account_id.id}
- }
-
def action_get_list_view(self):
self.ensure_one()
return {
@@ -899,7 +884,7 @@ class Project(models.Model):
'show': True,
'sequence': 3,
}]
- if self.user_has_groups('project.group_project_rating'):
+ if self.rating_count != 0 and self.user_has_groups('project.group_project_rating'):
if self.rating_avg >= rating_data.RATING_AVG_TOP:
icon = 'smile-o text-success'
elif self.rating_avg >= rating_data.RATING_AVG_OK:
@@ -2422,22 +2407,6 @@ class Task(models.Model):
return children
return children + children._get_all_subtasks(depth - 1)
- def get_milestone_to_mark_as_reached_action(self):
- """ Return an action if the milestone can be marked as reached otherwise return False """
- milestones = self.milestone_id.filtered('can_be_marked_as_done')
- if milestones:
- wizard = self.env['project.milestone.reach.wizard'].create({'line_ids': [Command.create({'milestone_id': m.id}) for m in milestones]})
- return {
- 'name': _('Mark milestone as reached'),
- 'view_mode': 'form',
- 'res_model': 'project.milestone.reach.wizard',
- 'views': [(self.env.ref('project.project_milestone_reach_wizard_view_form').id, 'form')],
- 'type': 'ir.actions.act_window',
- 'res_id': wizard.id,
- 'target': 'new',
- }
- return False
-
def action_open_parent_task(self):
return {
'name': _('Parent Task'),
diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv
index 08f048ceb24..3a32afac4a8 100644
--- a/addons/project/security/ir.model.access.csv
+++ b/addons/project/security/ir.model.access.csv
@@ -42,5 +42,3 @@ access_project_collaborator_user,project.collaborator.user,model_project_collabo
access_project_collaborator_portal,project.collaborator.portal,model_project_collaborator,base.group_portal,1,0,0,0
access_project_share_manager,project.share.wizard.manager,model_project_share_wizard,project.group_project_manager,1,1,1,0
access_project_personal_stage,project.personal.stage.user,model_project_task_stage_personal,base.group_user,1,1,1,1
-access_project_milestone_reach_wizard_project_user,project.milestone.reach.wizard.project.user,model_project_milestone_reach_wizard,project.group_project_user,1,1,1,0
-access_project_milestone_reach_line_wizard_project_user,project.milestone.reach.line.wizard.project.user,model_project_milestone_reach_line_wizard,project.group_project_user,1,1,1,0
diff --git a/addons/project/static/src/components/project_right_side_panel/project_right_side_panel.js b/addons/project/static/src/components/project_right_side_panel/project_right_side_panel.js
index 4737090703b..50f41722f68 100644
--- a/addons/project/static/src/components/project_right_side_panel/project_right_side_panel.js
+++ b/addons/project/static/src/components/project_right_side_panel/project_right_side_panel.js
@@ -1,7 +1,7 @@
/** @odoo-module */
import { useService } from '@web/core/utils/hooks';
-import { formatFloat, formatMonetary } from '@web/views/fields/formatters';
+import { formatFloat } from '@web/views/fields/formatters';
import { session } from '@web/session';
import { ViewButton } from '@web/views/view_button/view_button';
import { FormViewDialog } from '@web/views/view_dialogs/form_view_dialog';
@@ -70,9 +70,9 @@ export class ProjectRightSidePanel extends Component {
}
formatMonetary(value, options = {}) {
- const valueFormatted = formatMonetary(value, {
- currencyId: this.currencyId,
+ const valueFormatted = formatFloat(value, {
...options,
+ 'digits': [false, 0],
'noSymbol': true,
});
const currency = session.currencies[this.currencyId];
diff --git a/addons/project/static/src/components/subtask_one2many_field/subtask_list_renderer.js b/addons/project/static/src/components/subtask_one2many_field/subtask_list_renderer.js
deleted file mode 100644
index 519a008693a..00000000000
--- a/addons/project/static/src/components/subtask_one2many_field/subtask_list_renderer.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/** @odoo-module */
-
-import { useService } from "@web/core/utils/hooks";
-import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
-import { ListRenderer } from '@web/views/list/list_renderer';
-
-export class SubtaskListRenderer extends ListRenderer {
- setup() {
- super.setup();
- this.dialog = useService("dialog");
- }
-
- async onDeleteRecord(record) {
- this.dialog.add(ConfirmationDialog, {
- body: this.env._t("Are you sure you want to delete this record?"),
- confirm: () => super.onDeleteRecord(record),
- cancel: () => {},
- });
- }
-}
diff --git a/addons/project/static/src/components/subtask_one2many_field/subtask_one2many_field.js b/addons/project/static/src/components/subtask_one2many_field/subtask_one2many_field.js
deleted file mode 100644
index 5a60b32be68..00000000000
--- a/addons/project/static/src/components/subtask_one2many_field/subtask_one2many_field.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/** @odoo-module */
-
-import { registry } from "@web/core/registry";
-import { X2ManyField } from '@web/views/fields/x2many/x2many_field';
-
-import { SubtaskListRenderer } from './subtask_list_renderer';
-
-export class SubtaskOne2ManyField extends X2ManyField {
- get Renderer() {
- return this.viewMode === 'list' ? SubtaskListRenderer : super.Renderer;
- }
-}
-
-SubtaskOne2ManyField.components = {
- ...X2ManyField,
- ListRenderer: SubtaskListRenderer,
-}
-
-registry.category("fields").add("subtasks_one2many", SubtaskOne2ManyField);
diff --git a/addons/project/static/src/js/project_form.js b/addons/project/static/src/js/project_form.js
index 692ffa768d4..6dc7c270c83 100644
--- a/addons/project/static/src/js/project_form.js
+++ b/addons/project/static/src/js/project_form.js
@@ -111,25 +111,6 @@ const ProjectFormController = FormController.extend({
dialog.open();
},
- async _applyChanges(dataPointID, changes, event) {
- const result = await this._super(...arguments);
- if (event.data.force_save && 'stage_id' in changes) {
- this._getMilestoneReachWizardAction([parseInt(event.target.res_id)]);
- }
- return result;
- },
- async _getMilestoneReachWizardAction(recordIds) {
- const action = await this._rpc({
- model: 'project.task',
- method: 'get_milestone_to_mark_as_reached_action',
- args: [recordIds],
- });
- if (action) {
- this.trigger_up('do-action', {
- action,
- });
- }
- },
async _saveRecord(recordID, options) {
const task = this.model.get(recordID || this.handle);
const result = await this._super(...arguments);
diff --git a/addons/project/static/src/js/subtasks_list_renderer.js b/addons/project/static/src/js/subtasks_list_renderer.js
deleted file mode 100644
index 5eab262da0f..00000000000
--- a/addons/project/static/src/js/subtasks_list_renderer.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/** @odoo-module **/
-
-import ListRenderer from 'web.ListRenderer';
-import Dialog from 'web.Dialog';
-import { _t } from 'web.core';
-
-export const SubTasksListRenderer = ListRenderer.extend({
- events: Object.assign({}, ListRenderer.prototype.events, {
- 'click tr .o_list_record_remove': '_onClickOpenDialog',
- }),
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * Confirmation dialog is opened when deleted any record from list.
- *
- * @private
- * @param {MouseEvent} ev
- */
- _onClickOpenDialog(ev) {
- ev.stopPropagation();
- Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), {
- confirm_callback: () => {
- this._onRemoveIconClick(ev);
- },
- cancel_callback: () => {
- return false;
- },
- });
- },
-
-});
diff --git a/addons/project/static/src/js/widgets/subtasks_one2many_widget.js b/addons/project/static/src/js/widgets/subtasks_one2many_widget.js
deleted file mode 100644
index 3a23db8f3ec..00000000000
--- a/addons/project/static/src/js/widgets/subtasks_one2many_widget.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/** @odoo-module **/
-
-import { FieldOne2Many } from 'web.relational_fields';
-import fieldRegistry from 'web.field_registry';
-import { SubTasksListRenderer } from '../subtasks_list_renderer';
-
-const SubTasksFieldOne2Many = FieldOne2Many.extend({
- /**
- * We want to use our custom renderer for the list.
- *
- * @override
- */
- _getRenderer() {
- if (this.view.arch.tag === 'tree') {
- return SubTasksListRenderer;
- }
- return this._super.apply(...arguments);
- },
-});
-
-fieldRegistry.add('subtasks_one2many', SubTasksFieldOne2Many);
diff --git a/addons/project/tests/test_project_recurrence.py b/addons/project/tests/test_project_recurrence.py
index 7f3d68e86ae..4508545ec3f 100644
--- a/addons/project/tests/test_project_recurrence.py
+++ b/addons/project/tests/test_project_recurrence.py
@@ -472,12 +472,13 @@ class TestProjectrecurrence(TransactionCase):
'name': 'Parent Task',
'project_id': self.project_recurring.id
})
+ child_task = self.env['project.task'].create({
+ 'name': 'Child Task',
+ 'parent_id': parent_task.id,
+ })
domain = [('project_id', '=', self.project_recurring.id)]
- with Form(parent_task.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.new() as subtask_form:
- subtask_form.name = 'Test Subtask 1'
with freeze_time("2020-01-01"):
- with Form(parent_task.child_ids.with_context({'tracking_disable': True})) as form:
+ with Form(child_task.with_context({'tracking_disable': True})) as form:
form.description = 'my super recurring task bla bla bla'
form.date_deadline = datetime(2020, 2, 1)
form.display_project_id = parent_task.project_id
@@ -569,12 +570,20 @@ class TestProjectrecurrence(TransactionCase):
'project_id': self.project_recurring.id
})
domain = [('project_id', '=', self.project_recurring.id)]
+ child_task_1, child_task_2_recurrence = self.env['project.task'].create([
+ {'name': 'Child task 1'},
+ {'name': 'Child task 2 that have recurrence'},
+ ])
with Form(parent_task.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.new() as subtask_form:
- subtask_form.name = 'Child task 1'
- with Form(parent_task.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.new() as subtask_form:
- subtask_form.name = 'Child task 2 that have recurrence'
+ task_form.child_ids.add(child_task_1)
+ task_form.child_ids.add(child_task_2_recurrence)
+
+ grand_child_task_1 = self.env['project.task'].create({
+ 'name': 'Grandchild task 1 (recurrent)',
+ })
+ grand_child_task_2_recurrence = self.env['project.task'].create({
+ 'name': 'Grandchild task 2',
+ })
with freeze_time("2020-01-01"):
recurrent_subtask = parent_task.child_ids[0]
with Form(recurrent_subtask.with_context(tracking_disable=True)) as task_form:
@@ -586,11 +595,8 @@ class TestProjectrecurrence(TransactionCase):
task_form.repeat_on_month = 'date'
task_form.repeat_day = '15'
task_form.date_deadline = datetime(2020, 2, 1)
- with task_form.child_ids.new() as subtask_form:
- subtask_form.name = 'Grandchild task 1 (recurrent)'
- with task_form.child_ids.new() as subtask_form:
- subtask_form.name = 'Grandchild task 2'
-
+ task_form.child_ids.add(grand_child_task_1)
+ task_form.child_ids.add(grand_child_task_2_recurrence)
# configure recurring subtask
recurrent_subsubtask = recurrent_subtask.child_ids.filtered(lambda t: t.name == 'Grandchild task 1 (recurrent)')
non_recurrent_subsubtask = recurrent_subtask.child_ids.filtered(lambda t: t.name == 'Grandchild task 2')
@@ -602,18 +608,20 @@ class TestProjectrecurrence(TransactionCase):
subtask_form.repeat_number = 4
subtask_form.date_deadline = datetime(2020, 2, 3)
+ grand_child_task_3, grand_child_task_4, grand_child_task_5 = self.env['project.task'].create([
+ {'name': 'Grandchild task 3'},
+ {'name': 'Grandchild task 4'},
+ {'name': 'Grandchild task 5'},
+ ])
# create non-recurring grandchild subtasks
with Form(non_recurrent_subsubtask.with_context(tracking_disable=True)) as subtask_form:
- with subtask_form.child_ids.new() as subsubtask_form:
- subsubtask_form.name = 'Grandchild task 3'
+ subtask_form.child_ids.add(grand_child_task_3)
non_recurrent_subsubtask = non_recurrent_subsubtask.child_ids
with Form(non_recurrent_subsubtask.with_context(tracking_disable=True)) as subtask_form:
- with subtask_form.child_ids.new() as subsubtask_form:
- subsubtask_form.name = 'Grandchild task 4'
+ subtask_form.child_ids.add(grand_child_task_4)
non_recurrent_subsubtask = non_recurrent_subsubtask.child_ids
with Form(non_recurrent_subsubtask.with_context(tracking_disable=True)) as subtask_form:
- with subtask_form.child_ids.new() as subsubtask_form:
- subsubtask_form.name = 'Grandchild task 5'
+ subtask_form.child_ids.add(grand_child_task_5)
self.assertTrue(recurrent_subtask.recurrence_id)
self.assertEqual(recurrent_subtask.recurrence_id.next_recurrence_date, date(2020, 1, 15))
diff --git a/addons/project/tests/test_project_subtasks.py b/addons/project/tests/test_project_subtasks.py
index 66f134417fa..109a2c5fc9d 100644
--- a/addons/project/tests/test_project_subtasks.py
+++ b/addons/project/tests/test_project_subtasks.py
@@ -108,26 +108,25 @@ class TestProjectSubtasks(TestProjectCommon):
- The display project id should follow the project id
"""
# 1)
+ test_subtask_1 = self.env['project.task'].create({
+ 'name': 'Test Subtask 1',
+ })
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.new() as subtask_form:
- subtask_form.name = 'Test Subtask 1'
+ task_form.child_ids.add(test_subtask_1)
self.assertEqual(self.task_1.child_ids.project_id, self.project_pigs, "The project should be assigned from the default project.")
self.assertFalse(self.task_1.child_ids.display_project_id, "The display project of a sub task should be false to project_id.")
# 2)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.edit(0) as subtask_form:
- subtask_form.display_project_id = self.project_goats
-
+ task_form.child_ids[0].display_project_id = self.project_goats
self.assertEqual(self.task_1.project_id, self.project_pigs, "Changing the project of a subtask should not change parent project")
self.assertEqual(self.task_1.child_ids.display_project_id, self.project_goats, "Display Project of the task should be well assigned")
self.assertEqual(self.task_1.child_ids.project_id, self.project_goats, "Changing display project id on a subtask should change project id")
# 3)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.edit(0) as subtask_form:
- subtask_form.display_project_id = self.env['project.project']
+ task_form.child_ids[0].display_project_id = self.env['project.project']
self.assertFalse(self.task_1.child_ids.display_project_id, "Display Project of the task should be well assigned, to False")
self.assertEqual(self.task_1.child_ids.project_id, self.project_pigs, "Resetting display project to False on a subtask should change project id to parent project id")
@@ -142,8 +141,7 @@ class TestProjectSubtasks(TestProjectCommon):
# 5)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.edit(0) as subtask_form:
- subtask_form.display_project_id = self.project_goats
+ task_form.child_ids[0].display_project_id = self.project_goats
task_form.project_id = self.project_pigs
self.assertEqual(self.task_1.project_id, self.project_pigs, "Parent project should change back.")
@@ -162,10 +160,12 @@ class TestProjectSubtasks(TestProjectCommon):
self.assertFalse(orphan_subtask.parent_id, "Parent should be false")
# 7)
+ test_subtask_1 = self.env['project.task'].create({
+ 'name': 'Test Subtask 1',
+ })
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.new() as subtask_form:
- subtask_form.name = 'Test Subtask 1'
- subtask_form.display_project_id = self.project_goats
+ task_form.child_ids.add(test_subtask_1)
+ task_form.child_ids[0].display_project_id = self.project_goats
with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form:
subtask_form.display_project_id = self.env['project.project']
subtask_form.parent_id = self.env['project.task']
@@ -183,9 +183,11 @@ class TestProjectSubtasks(TestProjectCommon):
self.project_pigs.type_ids |= stage_a
self.project_pigs.type_ids |= stage_b
+ test_subtask_1 = self.env['project.task'].create({
+ 'name': 'Test Subtask 1',
+ })
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.new() as subtask_form:
- subtask_form.name = 'Test Subtask 1'
+ task_form.child_ids.add(test_subtask_1)
self.assertEqual(self.task_1.child_ids.stage_id, stage_a, "The stage of the child task should be the default one of the project.")
@@ -194,16 +196,17 @@ class TestProjectSubtasks(TestProjectCommon):
self.assertEqual(self.task_1.child_ids.stage_id, stage_a, "The stage of the child task should remain the same while changing parent task stage.")
+ test_subtask_2 = self.env['project.task'].create({
+ 'name': 'Test Subtask 2',
+ })
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
- task_form.child_ids.remove(0)
- with task_form.child_ids.new() as subtask_form:
- subtask_form.name = 'Test Subtask 2'
+ task_form.child_ids.remove(test_subtask_1.id)
+ task_form.child_ids.add(test_subtask_2)
self.assertEqual(self.task_1.child_ids.stage_id, stage_a, "The stage of the child task should be the default one of the project even if parent stage id is different.")
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.edit(0) as subtask_form:
- subtask_form.display_project_id = self.project_goats
+ task_form.child_ids[0].display_project_id = self.project_goats
self.assertEqual(self.task_1.child_ids.stage_id.name, "New", "The stage of the child task should be the default one of the display project id, once set.")
@@ -242,15 +245,24 @@ class TestProjectSubtasks(TestProjectCommon):
4) check the correct nb of sub-tasks is displayed in the 'sub-tasks' stat button and on the parent task kanban card
5) sub-tasks should be copied when the parent task is duplicated
"""
- with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
- with task_form.child_ids.new() as subtask_form:
- subtask_form.name = 'Test Subtask 1'
- subtask_form.display_project_id = self.env['project.project']
- child_subtask = self.task_1.child_ids[0]
- with Form(child_subtask.with_context(tracking_disable=True)) as subtask_form:
- with subtask_form.child_ids.new() as subsubtask_form:
- subsubtask_form.name = 'Test Subtask/Subtask'
+ test_subtask_1 = self.env['project.task'].create({
+ 'name': 'Test Subtask 1',
+ })
+
+ task_form = Form(self.task_1.with_context({'tracking_disable': True}))
+ task_form.child_ids.add(test_subtask_1)
+ task_form.child_ids[0].display_project_id = self.env['project.project']
task = task_form.save()
+
+ child_subtask = self.task_1.child_ids[0]
+ test_subtask_2 = self.env['project.task'].create({
+ 'name': 'Test Subtask 2',
+ })
+
+ with Form(child_subtask.with_context(tracking_disable=True)) as subtask_form:
+ subtask_form.child_ids.add(test_subtask_2)
+ subtask_form.child_ids[0].display_project_id = self.env['project.project']
+
self.assertEqual(task.subtask_count, 2, "Parent task should have 2 children")
task_2 = task.copy()
self.assertEqual(task_2.subtask_count, 2, "If the parent task is duplicated then the sub task should be copied")
diff --git a/addons/project/views/project_collaborator_views.xml b/addons/project/views/project_collaborator_views.xml
index 15e26993ae4..34cb04bdd09 100644
--- a/addons/project/views/project_collaborator_views.xml
+++ b/addons/project/views/project_collaborator_views.xml
@@ -1,21 +1,6 @@
-
- project.collaborator.view.form
- project.collaborator
-
-
-
-
-
project.collaborator.view.tree
project.collaborator
@@ -45,7 +30,7 @@
Project Collaborators
project.collaborator
- tree,form
+ tree
[('project_id', '=', active_id)]
diff --git a/addons/project/views/project_portal_templates.xml b/addons/project/views/project_portal_templates.xml
index 52eed546043..87519db6084 100644
--- a/addons/project/views/project_portal_templates.xml
+++ b/addons/project/views/project_portal_templates.xml
@@ -110,13 +110,15 @@
-
-
+
+
+
|
Name |
- Assignees |
+ Assignees |
+ Milestone |
|
Project |
Stage |
@@ -127,6 +129,12 @@
|
|
+
+
+ No Milestone
+ |
|
@@ -149,13 +157,18 @@
|
|
-
+ |
|
+
+
+
+
+ |
@@ -294,6 +307,7 @@
Deadline:
+ Milestone:
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index f039715c6b1..de48ef43c38 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -457,45 +457,6 @@
| |