[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:
@@ -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
|
||||
|
||||
# ---------------------------------------------------
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
# ---------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user