From 749810a2e110f37bbfb04a12bc08ccb0122dd6df Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Thu, 12 Sep 2019 14:37:49 +0000 Subject: [PATCH] [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 --- addons/project/models/project.py | 91 ++++++++++++------------- addons/project/views/project_views.xml | 7 +- addons/sale_timesheet/models/project.py | 33 +++++---- 3 files changed, 66 insertions(+), 65 deletions(-) diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 9629d9c7d5a..53a5befe9f1 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -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 # --------------------------------------------------- diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 7940111441a..f8abdfd8e80 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -22,6 +22,7 @@ + @@ -449,8 +450,7 @@