[IMP] project: delete useless fields

In this commit:
- project tasks kanban view: if partner_is_company, we display partner_id
else commercial_partner_id -> this information is useless so we decided to
display partner_id in both cases.
- manager_id is not used in any view -> drop the field
- when creating a task by mail, email_from is set to the sender email then will
be changed depending on partner_id and parent_id, the field name is not significant
as it change and no more contain the original email sender, it's also a duplicated
information that can be got from partner / parent -> delete the field and create
a new partner if no one already exist with the same email.
- when project.analytic_account_id is changed, only tasks that have the same old
analytic_account_id will follow the name value -> we decided to change the behavior
and apply the new value for all tasks.
- project_analytic_account_id not used in any view and in only place in python
-> delete field
- project.project: partner_email, partner_phone are not used in any view ->
delete field
- project.task: partner_email not used -> delete field
- ancestor_id is no more used in any view or python reference as it's a complicated
field to understand by users -> remove ancestor_id from project.task and remove also
ancestor_task_id that is related it to it in timesheet context and replace it with
parent_task_id which is more simpler to understand by users.

task-3103701

closes odoo/odoo#110101

Related: odoo/enterprise#37246
Related: odoo/upgrade#4269
Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
This commit is contained in:
Abderraouf Ghrissi (abgh)
2023-02-23 18:28:45 +01:00
parent 94c49cfdee
commit a424cf481c
12 changed files with 42 additions and 134 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ class AccountAnalyticLine(models.Model):
'project.task', 'Task', index='btree_not_null',
compute='_compute_task_id', store=True, readonly=False,
domain="[('company_id', '=', company_id), ('project_id.allow_timesheets', '=', True), ('project_id', '=?', project_id)]")
ancestor_task_id = fields.Many2one('project.task', related='task_id.ancestor_id', store=True, index='btree_not_null')
parent_task_id = fields.Many2one('project.task', related='task_id.parent_id', store=True)
project_id = fields.Many2one(
'project.project', 'Project', domain=_domain_project_id, index=True,
compute='_compute_project_id', store=True, readonly=False)
@@ -14,7 +14,7 @@ class TimesheetsAnalysisReport(models.Model):
user_id = fields.Many2one("res.users", string="User", readonly=True)
project_id = fields.Many2one("project.project", string="Project", readonly=True)
task_id = fields.Many2one("project.task", string="Task", readonly=True)
ancestor_task_id = fields.Many2one("project.task", string="Ancestor Task", readonly=True)
parent_task_id = fields.Many2one("project.task", string="Parent Task", readonly=True)
employee_id = fields.Many2one("hr.employee", string="Employee", readonly=True)
manager_id = fields.Many2one("hr.employee", "Manager", readonly=True)
company_id = fields.Many2one("res.company", string="Company", readonly=True)
@@ -37,7 +37,7 @@ class TimesheetsAnalysisReport(models.Model):
A.user_id AS user_id,
A.project_id AS project_id,
A.task_id AS task_id,
A.ancestor_task_id AS ancestor_task_id,
A.parent_task_id AS parent_task_id,
A.employee_id AS employee_id,
A.manager_id AS manager_id,
A.company_id AS company_id,
@@ -178,8 +178,8 @@
<field name="date"/>
<field name="employee_id"/>
<field name="project_id"/>
<field name="ancestor_task_id" groups="project.group_subtask_project"/>
<field name="task_id"/>
<field name="parent_task_id" groups="project.group_subtask_project"/>
<field name="name"/>
<field name="department_id"/>
<field name="manager_id"/>
@@ -188,7 +188,7 @@
<filter name="month" string="Date" date="date"/>
<group expand="0" string="Group By">
<filter string="Project" name="groupby_project" domain="[]" context="{'group_by': 'project_id'}"/>
<filter string="Ancestor Task" name="groupby_parent_task" domain="[]" context="{'group_by': 'ancestor_task_id'}" groups="project.group_subtask_project"/>
<filter string="Parent Task" name="groupby_parent_task" domain="[]" context="{'group_by': 'parent_task_id'}" groups="project.group_subtask_project"/>
<filter string="Task" name="groupby_task" domain="[]" context="{'group_by': 'task_id'}"/>
<filter string="Date" name="groupby_date" domain="[]" context="{'group_by': 'date'}" help="Timesheet by Date"/>
<filter string="Department" name="groupby_department" domain="[]" context="{'group_by': 'department_id'}"/>
+13 -84
View File
@@ -27,13 +27,11 @@ PROJECT_TASK_READABLE_FIELDS = {
'project_id',
'display_project_id',
'color',
'partner_is_company',
'commercial_partner_id',
'allow_subtasks',
'subtask_count',
'child_text',
'is_closed',
'email_from',
'create_date',
'write_date',
'company_id',
@@ -62,7 +60,6 @@ PROJECT_TASK_WRITABLE_FIELDS = {
'name',
'description',
'partner_id',
'partner_email',
'date_deadline',
'tag_ids',
'sequence',
@@ -317,12 +314,6 @@ class Project(models.Model):
help="If the active field is set to False, it will allow you to hide the project without removing it.")
sequence = fields.Integer(default=10)
partner_id = fields.Many2one('res.partner', string='Customer', auto_join=True, tracking=True, check_company=True)
partner_email = fields.Char(
compute='_compute_partner_email', inverse='_inverse_partner_email',
string='Email', readonly=False, store=True, copy=False)
partner_phone = fields.Char(
compute='_compute_partner_phone', inverse='_inverse_partner_phone',
string="Phone", readonly=False, store=True, copy=False)
commercial_partner_id = fields.Many2one(related="partner_id.commercial_partner_id")
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', related="company_id.currency_id", string="Currency", readonly=True)
@@ -436,28 +427,6 @@ class Project(models.Model):
('project_date_greater', 'check(date >= date_start)', "The project's start date must be before its end date.")
]
@api.depends('partner_id.email')
def _compute_partner_email(self):
for project in self:
if project.partner_id.email != project.partner_email:
project.partner_email = project.partner_id.email
def _inverse_partner_email(self):
for project in self:
if project.partner_id and project.partner_email != project.partner_id.email:
project.partner_id.email = project.partner_email
@api.depends('partner_id.phone')
def _compute_partner_phone(self):
for project in self:
if project.partner_phone != project.partner_id.phone:
project.partner_phone = project.partner_id.phone
def _inverse_partner_phone(self):
for project in self:
if project.partner_id and project.partner_phone != project.partner_id.phone:
project.partner_id.phone = project.partner_phone
@api.onchange('alias_enabled')
def _onchange_alias_name(self):
if not self.alias_enabled:
@@ -1203,17 +1172,12 @@ class Task(models.Model):
string='Customer', recursive=True, tracking=True,
compute='_compute_partner_id', store=True, readonly=False,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
partner_is_company = fields.Boolean(related='partner_id.is_company', readonly=True)
commercial_partner_id = fields.Many2one(related='partner_id.commercial_partner_id')
partner_email = fields.Char(
compute='_compute_partner_email', inverse='_inverse_partner_email',
string='Email', readonly=False, store=True, copy=False)
partner_phone = fields.Char(
compute='_compute_partner_phone', inverse='_inverse_partner_phone',
string="Phone", readonly=False, store=True, copy=False)
partner_city = fields.Char(related='partner_id.city', readonly=False)
email_cc = fields.Char(help='Email addresses that were in the CC of the incoming emails from this task and that are not currently linked to an existing customer.')
manager_id = fields.Many2one('res.users', string='Project Manager', related='project_id.user_id', readonly=True)
company_id = fields.Many2one(
'res.company', string='Company', compute='_compute_company_id', store=True, readonly=False,
required=True, copy=True, default=_default_company_id)
@@ -1229,13 +1193,10 @@ class Task(models.Model):
legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True)
is_closed = fields.Boolean(related="stage_id.fold", string="Closing Stage", store=True, index=True, help="Folded in Kanban stages are closing stages.")
parent_id = fields.Many2one('project.task', string='Parent Task', index=True)
ancestor_id = fields.Many2one('project.task', string='Ancestor Task', compute='_compute_ancestor_id', index='btree_not_null', recursive=True, store=True)
child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks", domain="[('recurring_task', '=', False)]")
child_text = fields.Char(compute="_compute_child_text")
allow_subtasks = fields.Boolean(string="Allow Sub-tasks", related="project_id.allow_subtasks", readonly=True)
subtask_count = fields.Integer("Sub-task Count", compute='_compute_subtask_count')
email_from = fields.Char(string='Email From', help="These people will receive email.", index='trigram',
compute='_compute_email_from', recursive=True, store=True, readonly=False, copy=False)
project_privacy_visibility = fields.Selection(related='project_id.privacy_visibility', string="Project Visibility")
# Computed field about working time elapsed between record creation and assignation/closing.
working_hours_open = fields.Float(compute='_compute_elapsed', string='Working Hours to Assign', digits=(16, 2), store=True, group_operator="avg")
@@ -1366,8 +1327,6 @@ class Task(models.Model):
help="Analytic account to which this task and its timesheets are linked.\n"
"Track the costs and revenues of your task by setting its analytic account on your related documents (e.g. sales orders, invoices, purchase orders, vendor bills, expenses etc.).\n"
"By default, the analytic account of the project is set. However, it can be changed on each task individually if necessary.")
is_analytic_account_id_changed = fields.Boolean('Is Analytic Account Manually Changed', compute='_compute_is_analytic_account_id_changed', store=True)
project_analytic_account_id = fields.Many2one('account.analytic.account', string='Project Analytic Account', related='project_id.analytic_account_id')
_sql_constraints = [
('recurring_task_has_no_parent', 'CHECK (NOT (recurring_task IS TRUE AND parent_id IS NOT NULL))', "A subtask cannot be recurrent.")
@@ -1383,15 +1342,8 @@ class Task(models.Model):
@api.depends('project_id.analytic_account_id')
def _compute_analytic_account_id(self):
self.env.remove_to_compute(self._fields['is_analytic_account_id_changed'], self)
for task in self:
if not task.is_analytic_account_id_changed:
task.analytic_account_id = task.project_id.analytic_account_id
@api.depends('analytic_account_id')
def _compute_is_analytic_account_id_changed(self):
for task in self:
task.is_analytic_account_id_changed = task.project_id and task.analytic_account_id != task.project_id.analytic_account_id
task.analytic_account_id = task.project_id.analytic_account_id
@api.depends('project_id', 'parent_id')
def _compute_is_private(self):
@@ -1413,11 +1365,6 @@ class Task(models.Model):
def _compute_kanban_state(self):
self.kanban_state = 'normal'
@api.depends('parent_id.ancestor_id')
def _compute_ancestor_id(self):
for task in self:
task.ancestor_id = task.parent_id.ancestor_id or task.parent_id
@api.depends_context('uid')
@api.depends('user_ids')
def _compute_personal_stage_id(self):
@@ -1598,17 +1545,6 @@ class Task(models.Model):
for task in self:
task.is_blocked = any(not blocking_task.is_closed or blocking_task.is_blocked for blocking_task in task.depend_on_ids)
@api.depends('partner_id.email')
def _compute_partner_email(self):
for task in self:
if task.partner_id.email != task.partner_email:
task.partner_email = task.partner_id.email
def _inverse_partner_email(self):
for task in self:
if task.partner_id and task.partner_email != task.partner_id.email:
task.partner_id.email = task.partner_email
@api.depends('partner_id.phone')
def _compute_partner_phone(self):
for task in self:
@@ -2128,7 +2064,10 @@ class Task(models.Model):
recurrence_update = vals.pop('recurrence_update', 'this')
if recurrence_update == 'future':
for task in self:
recurrent_task = task.ancestor_id if task.ancestor_id.recurring_task else task
ancestor_id = task
while ancestor_id.parent_id:
ancestor_id = ancestor_id.parent_id
recurrent_task = ancestor_id if ancestor_id.recurring_task else task
task_for_recurrence[recurrent_task.recurrence_id] = task
# The sudo is required for a portal user as the record update
@@ -2200,11 +2139,6 @@ class Task(models.Model):
project = task.display_project_id if task.parent_id and task.display_project_id else task.project_id
task.partner_id = self._get_default_partner_id(project, task.parent_id)
@api.depends('partner_id.email', 'parent_id.email_from')
def _compute_email_from(self):
for task in self:
task.email_from = task.partner_id.email or ((task.partner_id or task.parent_id) and task.email_from) or task.parent_id.email_from
@api.depends('parent_id.project_id', 'display_project_id')
def _compute_project_id(self):
# Avoid recomputing kanban_state
@@ -2465,6 +2399,13 @@ class Task(models.Model):
create_context['default_user_ids'] = False
if custom_values is None:
custom_values = {}
# Auto create partner if not existant when the task is created from email
if not msg.get('author_id') and msg.get('email_from'):
msg['author_id'] = self.env['res.partner'].create({
'email': msg['email_from'],
'name': msg['email_from'],
}).id
defaults = {
'name': msg.get('subject') or _("No Subject"),
'planned_hours': 0.0,
@@ -2491,8 +2432,6 @@ class Task(models.Model):
if task.partner_id:
reason = _('Customer Email') if task.partner_id.email else _('Customer')
task._message_add_suggested_recipient(recipients, partner=task.partner_id, reason=reason)
elif task.email_from:
task._message_add_suggested_recipient(recipients, email=task.email_from, reason=_('Customer Email'))
return recipients
def _notify_by_email_get_headers(self):
@@ -2511,16 +2450,6 @@ class Task(models.Model):
if image_attachments:
self.displayed_image_id = image_attachments[0]
if self.email_from and not self.partner_id:
# we consider that posting a message with a specified recipient (not a follower, a specific one)
# on a document without customer means that it was created through the chatter using
# suggested recipients. This heuristic allows to avoid ugly hacks in JS.
new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.email_from)
if new_partner:
self.search([
('partner_id', '=', False),
('email_from', '=', new_partner.email),
('is_closed', '=', False)]).write({'partner_id': new_partner.id})
# use the sanitized body of the email from the message thread to populate the task's description
if not self.description and message.subtype_id == self._creation_subtype() and self.partner_id == message.author_id:
self.description = message.body
@@ -2700,7 +2629,7 @@ class Task(models.Model):
# ---------------------------------------------------
def _get_task_analytic_account_id(self):
self.ensure_one()
return self.analytic_account_id or self.project_analytic_account_id
return self.analytic_account_id or self.project_id.analytic_account_id
@api.model
def get_unusual_days(self, date_from, date_to=None):
@@ -146,7 +146,7 @@ class ProjectTaskRecurrence(models.Model):
@api.model
def _get_recurring_fields(self):
return ['message_partner_ids', 'company_id', 'description', 'displayed_image_id', 'email_cc',
'parent_id', 'partner_email', 'partner_id', 'partner_phone', 'planned_hours',
'parent_id', 'partner_id', 'partner_phone', 'planned_hours',
'project_id', 'display_project_id', 'project_privacy_visibility', 'sequence', 'tag_ids', 'recurrence_id',
'name', 'recurring_task', 'analytic_account_id', 'user_ids']
+21
View File
@@ -124,6 +124,27 @@ class TestProjectFlow(TestProjectCommon, MailCommon):
self.assertEqual(task.project_id, self.project_goats, 'project_task: incorrect project')
self.assertEqual(task.stage_id.sequence, 1, "project_task: should have a stage with sequence=1")
@mute_logger('odoo.addons.mail.models.mail_thread')
def test_auto_create_partner(self):
email = 'unknown@test.com'
new_partner = self.env['res.partner'].search([('email', '=', email)])
self.assertFalse(new_partner)
task = self.format_and_process(
EMAIL_TPL, to='project+pigs@mydomain.com, valid.lelitre@agrolait.com',
cc='valid.other@gmail.com',
email_from=email,
subject='subject',
msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>',
target_model='project.task'
)
self.assertEqual(len(task), 1)
new_partner = self.env['res.partner'].search([('email', '=', email)])
self.assertTrue(new_partner)
self.assertEqual(task.partner_id, new_partner)
self.assertEqual(task.message_ids.author_id, new_partner)
def test_subtask_process(self):
"""
Check subtask mecanism and change it from project.
@@ -36,7 +36,6 @@
<field name="partner_id"/>
<field name="sequence"/>
<field name="is_closed"/>
<field name="partner_is_company"/>
<field name="displayed_image_id"/>
<field name="active"/>
<field name="allow_subtasks"/>
@@ -69,14 +68,8 @@
</span>
<br />
<t t-if="record.partner_id.value">
<span t-if="!record.partner_is_company.raw_value">
<field name="commercial_partner_id"/>
</span>
<span t-else="">
<field name="partner_id"/>
</span>
<field name="partner_id"/>
</t>
<t t-else="record.email_from.raw_value"><span><field name="email_from"/></span></t>
</div>
</div>
<div class="o_kanban_record_body">
+1 -10
View File
@@ -1322,12 +1322,10 @@
<page name="extra_info" string="Extra Info" groups="base.group_no_one">
<group>
<group>
<field name="is_analytic_account_id_changed" invisible="1"/>
<field name="parent_id" attrs="{'invisible': [('allow_subtasks', '=', False)]}" groups="base.group_no_one" context="{'search_view_ref' : 'project.view_task_search_form','search_default_display_project_id': project_id}"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting" context="{'default_partner_id': partner_id}"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="sequence" groups="base.group_no_one"/>
<field name="email_from" invisible="1"/>
<field name="email_cc" groups="base.group_no_one"/>
<field name="displayed_image_id" groups="base.group_no_one" options="{'no_create': True}"/>
</group>
@@ -1403,7 +1401,6 @@
<field name="partner_id"/>
<field name="sequence"/>
<field name="is_closed"/>
<field name="partner_is_company"/>
<field name="displayed_image_id"/>
<field name="active"/>
<field name="legend_blocked"/>
@@ -1445,14 +1442,8 @@
</span>
<br />
<t t-if="record.partner_id.value">
<span t-if="!record.partner_is_company.raw_value">
<field name="commercial_partner_id"/>
</span>
<span t-else="">
<field name="partner_id"/>
</span>
<field name="partner_id"/>
</t>
<t t-else="record.email_from.raw_value"><span><field name="email_from"/></span></t>
</div>
</div>
<div class="o_kanban_record_body">
@@ -124,7 +124,6 @@ class SaleOrderLine(models.Model):
project.tasks.write({
'sale_line_id': self.id,
'partner_id': self.order_id.partner_id.id,
'email_from': self.order_id.partner_id.email,
})
# duplicating a project doesn't set the SO on sub-tasks
project.tasks.filtered('parent_id').write({
@@ -162,7 +161,6 @@ class SaleOrderLine(models.Model):
'analytic_account_id': project.analytic_account_id.id,
'planned_hours': planned_hours,
'partner_id': self.order_id.partner_id.id,
'email_from': self.order_id.partner_id.email,
'description': description,
'project_id': project.id,
'sale_line_id': self.id,
@@ -53,11 +53,6 @@ class TestNestedTaskUpdate(TransactionCase):
child = self.env['project.task'].with_context(default_parent_id=parent.id).create({'name': 'child'})
self.assertEqual(child.partner_id, self.user.partner_id)
def test_creating_subtask_email_from_on_parent_goes_on_child(self):
parent = self.env['project.task'].create({'name': 'parent', 'email_from': 'a@c.be'})
child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id})
self.assertEqual(child.email_from, 'a@c.be')
def test_creating_subtask_sale_line_id_on_parent_goes_on_child_if_same_partner_in_values(self):
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.partner.id, 'sale_line_id': self.order_line.id, 'project_id': self.project.id})
child = self.env['project.task'].create({'name': 'child', 'partner_id': self.partner.id, 'parent_id': parent.id})
@@ -110,15 +105,6 @@ class TestNestedTaskUpdate(TransactionCase):
parent.write({'partner_id': False})
self.assertNotEqual(child.partner_id, self.user.partner_id)
def test_write_email_from_on_parent_write_on_child(self):
parent = self.env['project.task'].create({'name': 'parent'})
child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id})
self.assertFalse(child.email_from)
parent.write({'email_from': 'a@c.be'})
self.assertEqual(child.email_from, parent.email_from)
parent.write({'email_from': ''})
self.assertEqual(child.email_from, 'a@c.be')
def test_write_sale_line_id_on_parent_write_on_child_if_same_partner(self):
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.partner.id, 'project_id': self.project.id})
child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id, 'partner_id': self.partner.id})
@@ -166,13 +152,6 @@ class TestNestedTaskUpdate(TransactionCase):
child.write({'parent_id': parent.id})
self.assertEqual(child.partner_id, self.user.partner_id)
def test_linking_email_from_on_parent_write_on_child(self):
parent = self.env['project.task'].create({'name': 'parent', 'email_from': 'a@c.be'})
child = self.env['project.task'].create({'name': 'child', 'email_from': False})
self.assertFalse(child.email_from)
child.write({'parent_id': parent.id})
self.assertEqual(child.email_from, 'a@c.be')
def test_linking_sale_line_id_on_parent_write_on_child_if_same_partner(self):
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.partner.id, 'sale_line_id': self.order_line.id, 'project_id': self.project.id})
child = self.env['project.task'].create({'name': 'child', 'partner_id': self.partner.id})
@@ -39,7 +39,6 @@ class TestSaleService(TestCommonSaleTimesheet):
task = project.task_ids.filtered(lambda t: t.name == '%s - %s' % (self.sale_order.name, self.product_delivery_timesheet2.name))
self.assertTrue(task, 'Sale Service: task is not created, or it badly named')
self.assertEqual(task.partner_id, self.sale_order.partner_id, 'Sale Service: customer should be the same on task and on SO')
self.assertEqual(task.email_from, self.sale_order.partner_id.email, 'Sale Service: Task Email should be the same as the SO customer Email')
# log timesheet on task
self.env['account.analytic.line'].create({
@@ -159,7 +159,6 @@ class ProjectCreateSalesOrder(models.TransientModel):
task_ids.write({
'sale_line_id': sale_order_line.id,
'partner_id': sale_order.partner_id.id,
'email_from': sale_order.partner_id.email,
})
# assign SOL to timesheets
@@ -234,7 +233,6 @@ class ProjectCreateSalesOrder(models.TransientModel):
})
non_billable_tasks.write({
'partner_id': sale_order.partner_id.id,
'email_from': sale_order.partner_id.email,
})
# assign SOL to timesheets