[IMP] project: track dependencies in tasks

Tasks that are blocked by other tasks don't get any other information
from those tasks.
In order to get updates on the state of the blocking tasks we now track
some fields (determined by the `task_dependency_tracking` attribute) in
the parent's chatter.

Task ID: 2601448

closes odoo/odoo#73965

Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
William Braeckman
2021-08-18 11:19:38 +00:00
parent 22044ab0ad
commit d8a0912bbf
6 changed files with 121 additions and 4 deletions
+1
View File
@@ -36,6 +36,7 @@
'views/res_config_settings_views.xml',
'views/mail_activity_views.xml',
'views/project_portal_templates.xml',
'views/project_task_templates.xml',
'data/ir_cron_data.xml',
'data/mail_data.xml',
'data/mail_template_data.xml',
+15
View File
@@ -31,6 +31,12 @@
<field name="res_model">project.task</field>
<field name="default" eval="True"/>
</record>
<record id="mt_task_dependency_change" model="mail.message.subtype">
<field name="name">Task Dependency Changes</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="hidden" eval="True"/>
</record>
<!-- Project-related subtypes for messaging / Chatter -->
<record id="mt_project_task_new" model="mail.message.subtype">
<field name="name">Task Created</field>
@@ -72,4 +78,13 @@
<field name="parent_id" ref="mt_task_rating"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_dependency_change" model="mail.message.subtype">
<field name="name">Task Dependency Changes</field>
<field name="sequence">15</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" ref="mt_task_dependency_change"/>
<field name="relation_field">project_id</field>
<field name="hidden" eval="True"/>
</record>
</odoo>
+46 -4
View File
@@ -8,7 +8,7 @@ from datetime import timedelta, datetime
from random import randint
from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import UserError, AccessError, ValidationError, RedirectWarning
from odoo.exceptions import UserError, ValidationError
from odoo.osv.expression import OR
from .project_task_recurrence import DAYS, WEEKS
@@ -696,19 +696,19 @@ class Task(models.Model):
stage_id = fields.Many2one('project.task.type', string='Stage', compute='_compute_stage_id',
store=True, readonly=False, ondelete='restrict', tracking=True, index=True,
default=_get_default_stage_id, group_expand='_read_group_stage_ids',
domain="[('project_ids', '=', project_id)]", copy=False)
domain="[('project_ids', '=', project_id)]", copy=False, task_dependency_tracking=True)
tag_ids = fields.Many2many('project.tags', string='Tags')
kanban_state = fields.Selection([
('normal', 'In Progress'),
('done', 'Ready'),
('blocked', 'Blocked')], string='Status',
copy=False, default='normal', required=True)
kanban_state_label = fields.Char(compute='_compute_kanban_state_label', string='Kanban State Label', tracking=True)
kanban_state_label = fields.Char(compute='_compute_kanban_state_label', string='Kanban State Label', tracking=True, task_dependency_tracking=True)
create_date = fields.Datetime("Created On", readonly=True, index=True)
write_date = fields.Datetime("Last Updated On", readonly=True, index=True)
date_end = fields.Datetime(string='Ending Date', index=True, copy=False)
date_assign = fields.Datetime(string='Assigning Date', index=True, copy=False, readonly=True)
date_deadline = fields.Date(string='Deadline', index=True, copy=False, tracking=True)
date_deadline = fields.Date(string='Deadline', index=True, copy=False, tracking=True, task_dependency_tracking=True)
date_last_stage_update = fields.Datetime(string='Last Stage Update',
index=True,
copy=False,
@@ -1103,6 +1103,19 @@ class Task(models.Model):
)
return super(Task, self).get_empty_list_help(help)
def _valid_field_parameter(self, field, name):
# If the field has `task_dependency_tracking` on we track the changes made in the dependent task on the parent task
return name == 'task_dependency_tracking' or super()._valid_field_parameter(field, name)
@tools.ormcache('self.env.uid', 'self.env.su')
def _get_depends_tracked_fields(self):
""" Returns the set of tracked field names for the current model.
Those fields are the ones tracked in the parent task when using task dependencies.
See :meth:`mail.models.MailThread._get_tracked_fields`"""
fields = {name for name, field in self._fields.items() if getattr(field, 'task_dependency_tracking', None)}
return fields and set(self.fields_get(fields))
# ----------------------------------------
# Case management
# ----------------------------------------
@@ -1305,6 +1318,35 @@ class Task(models.Model):
# Mail gateway
# ---------------------------------------------------
def _mail_track(self, tracked_fields, initial_values):
result = super()._mail_track(tracked_fields, initial_values)
changes, tracking_value_ids = result
# Track changes on depending tasks
depends_tracked_fields = self._get_depends_tracked_fields()
depends_changes = changes & depends_tracked_fields
if depends_changes and self.user_has_groups('project.group_project_task_dependencies') and self.allow_task_dependencies:
parent_ids = self.env['project.task'].search([('depend_on_ids', 'in', self.ids)])
if parent_ids:
fields_to_ids = self.env['ir.model.fields']._get_ids('project.task')
field_ids = [fields_to_ids.get(name) for name in depends_changes]
depends_tracking_value_ids = [
tracking_values for tracking_values in tracking_value_ids
if tracking_values[2]['field'] in field_ids
]
subtype = self.env.ref('project.mt_task_dependency_change')
# We want to include the original subtype message coming from the child task
# for example when the stage changes the message in the chatter starts with 'Stage Changed'
child_subtype = self._track_subtype(dict((col_name, initial_values[col_name]) for col_name in changes))
child_subtype_info = child_subtype.description or child_subtype.name if child_subtype else False
# NOTE: the subtype does not have a description on purpose, otherwise the description would be put
# at the end of the message instead of at the top, we use the name here
body = self.env['ir.qweb']._render('project.task_track_depending_tasks', {
'child': self,
'child_subtype': child_subtype_info,
})
parent_ids.message_post(body=body, subtype_id=subtype.id, tracking_value_ids=depends_tracking_value_ids)
return result
def _track_template(self, changes):
res = super(Task, self)._track_template(changes)
test_task = self[0]
@@ -44,4 +44,11 @@ class ResConfigSettings(models.TransientModel):
# update for basic projects
update_projects(projects.filtered_domain(self._get_basic_project_domain()), basic_project_features)
# Hide the task dependency changes subtype when the dependency setting is disabled
task_dep_change_subtype_id = self.env.ref('project.mt_task_dependency_change')
project_task_dep_change_subtype_id = self.env.ref('project.mt_project_task_dependency_change')
if task_dep_change_subtype_id.hidden != (not self['group_project_task_dependencies']):
task_dep_change_subtype_id.hidden = not self['group_project_task_dependencies']
project_task_dep_change_subtype_id.hidden = not self['group_project_task_dependencies']
super(ResConfigSettings, self).set_values()
@@ -6,6 +6,7 @@ from odoo.tests import tagged
from odoo.addons.project.tests.test_project_base import TestProjectCommon
from datetime import date
@tagged('-at_install', 'post_install', 'task_dependencies')
class TestTaskDependencies(TestProjectCommon):
@@ -23,6 +24,11 @@ class TestTaskDependencies(TestProjectCommon):
'project_id': cls.project_pigs.id,
})
def flush_tracking(self):
""" Force the creation of tracking values. """
self.env['base'].flush()
self.cr.precommit.run()
def test_task_dependencies(self):
""" Test the task dependencies feature
@@ -85,3 +91,39 @@ class TestTaskDependencies(TestProjectCommon):
'depend_on_ids': [Command.link(self.task_1.id)],
})
self.assertEqual(len(self.task_2.depend_on_ids), 1, "The number of dependencies should no change in the task 2 because of a cyclic dependency.")
def test_tracking_dependencies(self):
# Enable the company setting
self.env['res.config.settings'].create({
'group_project_task_dependencies': True
}).execute()
self.task_1.write({
'depend_on_ids': [Command.link(self.task_2.id)]
})
self.cr.precommit.clear()
# Check that changing a dependency tracked field in task_2 logs a message in task_1.
self.task_2.write({'date_deadline': date(1983, 3, 1)}) # + 1 message in task_1 and task_2
self.flush_tracking()
self.assertEqual(len(self.task_1.message_ids), 1,
'Changing the deadline on task 2 should have logged a message in task 1.')
# Check that changing a dependency tracked field in task_1 does not log a message in task_2.
self.task_1.date_deadline = date(2020, 1, 2) # + 1 message in task_1
self.flush_tracking()
self.assertEqual(len(self.task_2.message_ids), 1,
'Changing the deadline on task 1 should not have logged a message in task 2.')
# Check that changing a field that is not tracked at all on task 2 does not impact task 1.
self.task_2.color = 100 # no new message
self.flush_tracking()
self.assertEqual(len(self.task_1.message_ids), 2,
'Changing the color on task 2 should not have logged a message in task 1 since it is not tracked.')
# Check that changing multiple fields does not log more than one message.
self.task_2.write({
'date_deadline': date(2020, 1, 1),
'kanban_state': 'blocked',
}) # + 1 message in task_1 and task_2
self.flush_tracking()
self.assertEqual(len(self.task_1.message_ids), 3,
'Changing multiple fields on task 2 should only log one message in task 1.')
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="task_track_depending_tasks">
<span>Task: <a href="#" data-oe-model="project.task" t-att-data-oe-id="child.id" t-esc="child.name"/></span>
<br/>
<t t-if="child_subtype">
<span><t t-esc="child_subtype"/></span><br/>
</t>
</template>
</odoo>