[IMP] project: substask can live their own life

This commit completes what was initiated a few time ago : a substask
is like a normal task and can now live its own life, being in another
project, attached to another customer, billed on another sale line, ...
than its mother.
The informations coming from the parent are only use as default values to
prefill the form view (with default options on creation, or though `onchange`
when setting the parent on existing task).

No values are forced now.

Form, default context, and others views has been adapted to make this feature
works.

Task-1982072
This commit is contained in:
jem-odoo
2019-09-27 11:43:13 +00:00
parent 5c8f83ef6b
commit 749810a2e1
3 changed files with 66 additions and 65 deletions
+44 -47
View File
@@ -436,9 +436,13 @@ class Task(models.Model):
@api.model
def default_get(self, fields_list):
result = super(Task, self).default_get(fields_list)
# force some parent values, if needed
if 'parent_id' in result and result['parent_id']:
result.update(self._subtask_values_from_parent(result['parent_id']))
# find default value from parent for the not given ones
parent_task_id = result.get('parent_id') or self._context.get('default_parent_id')
if parent_task_id:
parent_values = self._subtask_values_from_parent(parent_task_id)
for fname, value in parent_values.items():
if fname not in result:
result[fname] = value
return result
@api.model
@@ -538,11 +542,6 @@ class Task(models.Model):
# customer portal: include comment and incoming emails in communication history
website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
@api.constrains('parent_id')
def _check_parent_id(self):
if not self._check_recursion():
raise ValidationError(_('You cannot create recursive tasks.'))
def _compute_attachment_ids(self):
for task in self:
attachment_ids = self.env['ir.attachment'].search([('res_id', '=', task.id), ('res_model', '=', 'project.task')]).ids
@@ -619,14 +618,17 @@ class Task(models.Model):
@api.onchange('parent_id')
def _onchange_parent_id(self):
if self.parent_id:
for field_name in self._subtask_implied_fields():
self[field_name] = self.parent_id[field_name]
for field_name, value in self._subtask_values_from_parent(self.parent_id.id).items():
if not self[field_name]:
self[field_name] = value
@api.onchange('project_id')
def _onchange_project(self):
if self.project_id:
if not self.parent_id and self.project_id.partner_id:
# find partner
if self.project_id.partner_id:
self.partner_id = self.project_id.partner_id
# find stage
if self.project_id not in self.stage_id.project_ids:
self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)])
# keep multi company consistency
@@ -709,10 +711,6 @@ class Task(models.Model):
def create(self, vals):
# context: no_log, because subtype already handle this
context = dict(self.env.context)
# force some parent values, if needed
if 'parent_id' in vals and vals['parent_id']:
vals.update(self._subtask_values_from_parent(vals['parent_id']))
context.pop('default_parent_id', None)
# for default stage
if vals.get('project_id') and not context.get('default_project_id'):
context['default_project_id'] = vals.get('project_id')
@@ -723,14 +721,16 @@ class Task(models.Model):
if vals.get('stage_id'):
vals.update(self.update_date_end(vals['stage_id']))
vals['date_last_stage_update'] = fields.Datetime.now()
# substask default values
if vals.get('parent_id'):
for fname, value in self._subtask_values_from_parent(vals['parent_id']).items():
if fname not in vals:
vals[fname] = value
task = super(Task, self.with_context(context)).create(vals)
return task
def write(self, vals):
now = fields.Datetime.now()
# subtask: force some parent values, if needed
if 'parent_id' in vals and vals['parent_id']:
vals.update(self._subtask_values_from_parent(vals['parent_id']))
# stage change: update date_last_stage_update
if 'stage_id' in vals:
vals.update(self.update_date_end(vals['stage_id']))
@@ -746,12 +746,6 @@ class Task(models.Model):
# rating on stage
if 'stage_id' in vals and vals.get('stage_id'):
self.filtered(lambda x: x.project_id.rating_status == 'stage')._send_task_rating_mail(force_send=True)
# subtask: update subtask according to parent values
subtask_values_to_write = self._subtask_write_values(vals)
if subtask_values_to_write:
subtasks = self.filtered(lambda task: not task.parent_id).mapped('child_ids')
if subtasks:
subtasks.write(subtask_values_to_write)
return result
def update_date_end(self, stage_id):
@@ -764,27 +758,18 @@ class Task(models.Model):
# Subtasks
# ---------------------------------------------------
@api.model
def _subtask_implied_fields(self):
""" Return the list of field name to apply on subtask when changing parent_id or when updating parent task. """
def _subtask_default_fields(self):
""" Return the list of field name for default value when creating a subtask """
return ['partner_id', 'email_from']
def _subtask_write_values(self, values):
""" Return the values to write on subtask when `values` is written on parent tasks
:param values: dict of values to write on parent
"""
result = {}
for field_name in self._subtask_implied_fields():
if field_name in values:
result[field_name] = values[field_name]
return result
def _subtask_values_from_parent(self, parent_id):
""" Get values for substask implied field of the given"""
result = {}
parent_task = self.env['project.task'].browse(parent_id)
for field_name in self._subtask_implied_fields():
for field_name in self._subtask_default_fields():
result[field_name] = parent_task[field_name]
# special case for the subtask default project
result['project_id'] = parent_task.project_id.subtask_project_id
return self._convert_to_write(result)
# ---------------------------------------------------
@@ -942,16 +927,28 @@ class Task(models.Model):
def action_subtask(self):
action = self.env.ref('project.project_task_action_sub_task').read()[0]
ctx = self.env.context.copy()
ctx.update({
'default_parent_id': self.id,
'default_project_id': self.env.context.get('project_id', self.project_id.id),
'default_name': self.env.context.get('name', self.name) + ':',
'default_partner_id': self.env.context.get('partner_id', self.partner_id.id),
'search_default_project_id': self.env.context.get('project_id', self.project_id.id),
})
action['context'] = ctx
# only display subtasks of current task
action['domain'] = [('id', 'child_of', self.id), ('id', '!=', self.id)]
# update context, with all default values as 'quick_create' does not contains all field in its view
if self._context.get('default_project_id'):
default_project = self.env['project.project'].browse(self.env.context['default_project_id'])
else:
default_project = self.project_id.subtask_project_id or self.project_id
ctx = dict(self.env.context)
ctx.update({
'default_name': self.env.context.get('name', self.name) + ':',
'default_parent_id': self.id, # will give default subtask field in `default_get`
'default_company_id': default_project.company_id.id if default_project else self.env.company.id,
'search_default_parent_id': self.id,
})
parent_values = self._subtask_values_from_parent(self.id)
for fname, value in parent_values.items():
if 'default_' + fname not in ctx:
ctx['default_' + fname] = value
action['context'] = ctx
return action
# ---------------------------------------------------
+4 -3
View File
@@ -22,6 +22,7 @@
<field name="partner_id" operator="child_of"/>
<field name="stage_id"/>
<field name="project_id"/>
<field name="parent_id" groups="project.group_subtask_project"/>
<filter string="My Tasks" name="my_tasks" domain="[('user_id', '=', uid)]"/>
<filter string="Followed Tasks" name="my_followed_tasks" domain="[('message_is_follower', '=', True)]" />
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
@@ -449,8 +450,7 @@
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" icon="fa-tasks" type="object" name="action_open_parent_task" string="Parent Task" attrs="{'invisible' : [('parent_id', '=', False)]}" groups="project.group_subtask_project"/>
<button name="action_subtask" type="object" class="oe_stat_button" icon="fa-tasks"
attrs="{'invisible' : [('parent_id', '!=', False)]}"
context="{'project_id': subtask_project_id, 'name': name, 'partner_id': partner_id}" groups="project.group_subtask_project">
attrs="{'invisible' : ['|', ('parent_id', '!=', False), ('id', '=', False)]}" context="{'default_user_id': user_id, 'default_parent_id': id, 'default_project_id': subtask_project_id}" groups="project.group_subtask_project">
<field string="Sub-tasks" name="subtask_count" widget="statinfo"/>
</button>
<button name="%(rating_rating_action_task)d" type="action" attrs="{'invisible': [('rating_count', '=', 0)]}" class="oe_stat_button" icon="fa-smile-o" groups="project.group_project_rating">
@@ -490,7 +490,7 @@
<group>
<group>
<field name="sequence" groups="base.group_no_one"/>
<field name="partner_id" attrs="{'readonly': [('parent_id', '!=', False)]}"/>
<field name="partner_id"/>
<field name="email_from" invisible="1"/>
<field name="email_cc" groups="base.group_no_one"/>
<field name="parent_id" attrs="{'invisible' : [('subtask_count', '>', 0)]}" groups="project.group_subtask_project"/>
@@ -542,6 +542,7 @@
<group>
<field name="name" string = "Task Title"/>
<field name="user_id" options="{'no_open': True,'no_create': True}"/>
<field name="parent_id" invisible="1"/>
</group>
</form>
</field>
+18 -15
View File
@@ -183,10 +183,15 @@ class ProjectTask(models.Model):
@api.onchange('project_id')
def _onchange_project(self):
result = super(ProjectTask, self)._onchange_project()
if self.project_id.sale_line_id:
self.sale_line_id = self.project_id.sale_line_id
if not self.parent_id and not self.partner_id:
self.partner_id = self.sale_line_id.order_partner_id
if self.project_id:
if self.project_id.billable_type == 'employee_rate':
if not self.partner_id:
self.partner_id = self.project_id.sale_order_id.partner_id
elif self.project_id.billable_type == 'task_rate':
if not self.sale_line_id:
self.sale_line_id = self.project_id.sale_line_id
if not self.partner_id:
self.partner_id = self.sale_line_id.order_partner_id
# set domain on SO: on non billable project, all SOL of customer, otherwise the one from the SO
result = result or {}
domain = [('is_service', '=', True), ('is_expense', '=', False), ('order_partner_id', 'child_of', self.partner_id.commercial_partner_id.id), ('state', 'in', ['sale', 'done'])]
@@ -205,6 +210,13 @@ class ProjectTask(models.Model):
result.setdefault('domain', {})['sale_line_id'] = [('is_service', '=', True), ('is_expense', '=', False), ('order_partner_id', 'child_of', self.partner_id.commercial_partner_id.id), ('state', 'in', ['sale', 'done'])]
return result
@api.onchange('parent_id')
def _onchange_parent_id(self):
super(ProjectTask, self)._onchange_parent_id()
# check sale_line_id and customer are coherent
if self.sale_line_id and self.partner_id != self.sale_line_id.order_partner_id:
self.sale_line_id = False
@api.constrains('sale_line_id')
def _check_sale_line_type(self):
for task in self.sudo():
@@ -229,19 +241,10 @@ class ProjectTask(models.Model):
# ---------------------------------------------------
@api.model
def _subtask_implied_fields(self):
result = super(ProjectTask, self)._subtask_implied_fields()
def _subtask_default_fields(self):
result = super(ProjectTask, self)._subtask_default_fields()
return result + ['sale_line_id']
def _subtask_write_values(self, values):
result = super(ProjectTask, self)._subtask_write_values(values)
# changing the partner on a task will reset the sale line of its subtasks
if 'partner_id' in result:
result['sale_line_id'] = False
elif 'sale_line_id' in result:
result.pop('sale_line_id')
return result
# ---------------------------------------------------
# Actions
# ---------------------------------------------------