Purpose ======= During the development of the task #2169100, it appeared that the synchronisation of a task's partner_id according to its project's partner_id and its parent's partner_id was not very clear. A first idea was to simplify the partner_id management: as it could be annoying for the customer to see all the task's partner_id overriden after project's partner_id modification, the idea was to just use the parent's parnter_id or the project's partner_id as default value of the task's partner_id. Then, if the partner_id of the project or of the parent is changed, there is no more impact on the task partner_id itself. The main problem of this solution is that the management of the task's partner_id is also extended in several others modules like sale_project and sale_timesheet. You can find below what seems to happen with all these task partners. Start following rules according to which module is installed. (for the record: sale_timesheet depends on sale_project which depends on project). Rules are applied in the order they are written. Rules labelled "O" only apply when changing the project through the task form view. Rules labelled "C" apply everywhere. technical: C=compute, O=onchange project C.1 If parent's partner changes and the partner is not set, then set the parent's partner C.2 Else if project's partner changes ant the partner is not set, then set the project's partner sale_project [+ project] C.1 If the project's sale order line's partner changes and the partner is not set, then set the project's sale order line's partner C.2 Else apply (project.C) rules sale_timesheet + [sale_project + project] C.1 Apply (sale_project.C) rules O.1 If the project is billed "At Project rate" or "At Employee Rate" and the partner is not set, then set the project's sale order's partner the sale order line's partner Specification ============= - If the partner changes, we ignore all current tasks and let them be. We set the new partner as default value on all new tasks. In detail, we have done this: - When the user creates a task give the customer of the default project or the task parent as default customer for the task. - When the user changes the project and no partner in the task, we take the partner of the new project (if this project has a customer). - When the user changes the customer of the project, the linked tasks are not impacted by this changes, even if some tasks have not a customer yet. - When the user changes the customerf of the parent task, the child tasks are not impacted by this changes, event if these tasks have not a customer yet. - remove the onchange rule to remain consistent. TaskID: 2232042 closes #51471 Co-authored-by: Xavier BOL (xbo) <xbo@odoo.com>
300 lines
14 KiB
Python
300 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import base64
|
|
|
|
from .test_project_base import TestProjectCommon
|
|
from odoo.tools import mute_logger
|
|
from odoo.modules.module import get_resource_path
|
|
|
|
|
|
EMAIL_TPL = """Return-Path: <whatever-2a840@postmaster.twitter.com>
|
|
X-Original-To: {to}
|
|
Delivered-To: {to}
|
|
To: {to}
|
|
cc: {cc}
|
|
Received: by mail1.odoo.com (Postfix, from userid 10002)
|
|
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
|
|
Message-ID: {msg_id}
|
|
Date: Tue, 29 Nov 2011 12:43:21 +0530
|
|
From: {email_from}
|
|
MIME-Version: 1.0
|
|
Subject: {subject}
|
|
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
|
|
|
|
Hello,
|
|
|
|
This email should create a new entry in your module. Please check that it
|
|
effectively works.
|
|
|
|
Thanks,
|
|
|
|
--
|
|
Raoul Boitempoils
|
|
Integrator at Agrolait"""
|
|
|
|
|
|
class TestProjectFlow(TestProjectCommon):
|
|
|
|
def test_project_process_project_manager_duplicate(self):
|
|
pigs = self.project_pigs.with_user(self.user_projectmanager)
|
|
dogs = pigs.copy()
|
|
self.assertEqual(len(dogs.tasks), 2, 'project: duplicating a project must duplicate its tasks')
|
|
|
|
@mute_logger('odoo.addons.mail.mail_thread')
|
|
def test_task_process_without_stage(self):
|
|
# Do: incoming mail from an unknown partner on an alias creates a new task 'Frogs'
|
|
task = self.format_and_process(
|
|
EMAIL_TPL, to='project+pigs@mydomain.com, valid.lelitre@agrolait.com', cc='valid.other@gmail.com',
|
|
email_from='%s' % self.user_projectuser.email,
|
|
subject='Frogs', msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>',
|
|
target_model='project.task')
|
|
|
|
# Test: one task created by mailgateway administrator
|
|
self.assertEqual(len(task), 1, 'project: message_process: a new project.task should have been created')
|
|
# Test: check partner in message followers
|
|
self.assertIn(self.partner_2, task.message_partner_ids, "Partner in message cc is not added as a task followers.")
|
|
# Test: messages
|
|
self.assertEqual(len(task.message_ids), 1,
|
|
'project: message_process: newly created task should have 1 message: email')
|
|
self.assertEqual(task.message_ids[0].subtype_id, self.env.ref('project.mt_task_new'),
|
|
'project: message_process: first message of new task should have Task Created subtype')
|
|
self.assertEqual(task.message_ids[0].author_id, self.user_projectuser.partner_id,
|
|
'project: message_process: second message should be the one from Agrolait (partner failed)')
|
|
self.assertEqual(task.message_ids[0].subject, 'Frogs',
|
|
'project: message_process: second message should be the one from Agrolait (subject failed)')
|
|
# Test: task content
|
|
self.assertEqual(task.name, 'Frogs', 'project_task: name should be the email subject')
|
|
self.assertEqual(task.project_id.id, self.project_pigs.id, 'project_task: incorrect project')
|
|
self.assertEqual(task.stage_id.sequence, False, "project_task: shouldn't have a stage, i.e. sequence=False")
|
|
|
|
@mute_logger('odoo.addons.mail.mail_thread')
|
|
def test_task_process_with_stages(self):
|
|
# Do: incoming mail from an unknown partner on an alias creates a new task 'Cats'
|
|
task = self.format_and_process(
|
|
EMAIL_TPL, to='project+goats@mydomain.com, valid.lelitre@agrolait.com', cc='valid.other@gmail.com',
|
|
email_from='%s' % self.user_projectuser.email,
|
|
subject='Cats', msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>',
|
|
target_model='project.task')
|
|
|
|
# Test: one task created by mailgateway administrator
|
|
self.assertEqual(len(task), 1, 'project: message_process: a new project.task should have been created')
|
|
# Test: check partner in message followers
|
|
self.assertIn(self.partner_2, task.message_partner_ids, "Partner in message cc is not added as a task followers.")
|
|
# Test: messages
|
|
self.assertEqual(len(task.message_ids), 1,
|
|
'project: message_process: newly created task should have 1 messages: email')
|
|
self.assertEqual(task.message_ids[0].subtype_id, self.env.ref('project.mt_task_new'),
|
|
'project: message_process: first message of new task should have Task Created subtype')
|
|
self.assertEqual(task.message_ids[0].author_id, self.user_projectuser.partner_id,
|
|
'project: message_process: first message should be the one from Agrolait (partner failed)')
|
|
self.assertEqual(task.message_ids[0].subject, 'Cats',
|
|
'project: message_process: first message should be the one from Agrolait (subject failed)')
|
|
# Test: task content
|
|
self.assertEqual(task.name, 'Cats', 'project_task: name should be the email subject')
|
|
self.assertEqual(task.project_id.id, self.project_goats.id, 'project_task: incorrect project')
|
|
self.assertEqual(task.stage_id.sequence, 1, "project_task: should have a stage with sequence=1")
|
|
|
|
def test_subtask_process(self):
|
|
"""
|
|
Check subtask mecanism and change it from project.
|
|
|
|
For this test, 2 projects are used:
|
|
- the 'pigs' project which has a partner_id
|
|
- the 'goats' project where the partner_id is removed at the beginning of the tests and then restored.
|
|
|
|
2 parent tasks are also used to be able to switch the parent task of a sub-task:
|
|
- 'parent_task' linked to the partner_2
|
|
- 'another_parent_task' linked to the partner_3
|
|
"""
|
|
|
|
Task = self.env['project.task'].with_context({'tracking_disable': True})
|
|
|
|
parent_task = Task.create({
|
|
'name': 'Mother Task',
|
|
'user_id': self.user_projectuser.id,
|
|
'project_id': self.project_pigs.id,
|
|
'partner_id': self.partner_2.id,
|
|
'planned_hours': 12,
|
|
})
|
|
|
|
another_parent_task = Task.create({
|
|
'name': 'Another Mother Task',
|
|
'user_id': self.user_projectuser.id,
|
|
'project_id': self.project_pigs.id,
|
|
'partner_id': self.partner_3.id,
|
|
'planned_hours': 0,
|
|
})
|
|
|
|
# remove the partner_id of the 'goats' project
|
|
goats_partner_id = self.project_goats.partner_id
|
|
|
|
self.project_goats.write({
|
|
'partner_id': False
|
|
})
|
|
|
|
# the child task 1 is linked to a project without partner_id (goats project)
|
|
child_task_1 = Task.with_context(default_project_id=self.project_goats.id, default_parent_id=parent_task.id).create({
|
|
'name': 'Task Child with project',
|
|
'planned_hours': 3,
|
|
})
|
|
|
|
# the child task 2 is linked to a project with a partner_id (pigs project)
|
|
child_task_2 = Task.create({
|
|
'name': 'Task Child without project',
|
|
'parent_id': parent_task.id,
|
|
'project_id': self.project_pigs.id,
|
|
'display_project_id': self.project_pigs.id,
|
|
'planned_hours': 5,
|
|
})
|
|
|
|
self.assertEqual(
|
|
child_task_1.partner_id, child_task_1.parent_id.partner_id,
|
|
"When no project partner_id has been set, a subtask should have the same partner as its parent")
|
|
|
|
self.assertEqual(
|
|
child_task_2.partner_id, child_task_2.parent_id.partner_id,
|
|
"When a project partner_id has been set, a subtask should have the same partner as its parent")
|
|
|
|
self.assertEqual(
|
|
parent_task.subtask_count, 2,
|
|
"Parent task should have 2 children")
|
|
|
|
self.assertEqual(
|
|
parent_task.subtask_planned_hours, 8,
|
|
"Planned hours of subtask should impact parent task")
|
|
|
|
# change the parent of a subtask without a project partner_id
|
|
child_task_1.write({
|
|
'parent_id': another_parent_task.id
|
|
})
|
|
|
|
self.assertEqual(
|
|
child_task_1.partner_id, parent_task.partner_id,
|
|
"When changing the parent task of a subtask with no project partner_id, the partner_id should remain the same.")
|
|
|
|
# change the parent of a subtask with a project partner_id
|
|
child_task_2.write({
|
|
'parent_id': another_parent_task.id
|
|
})
|
|
|
|
self.assertEqual(
|
|
child_task_2.partner_id, parent_task.partner_id,
|
|
"When changing the parent task of a subtask with a project, the partner_id should remain the same.")
|
|
|
|
# set a project with partner_id to a subtask without project partner_id
|
|
child_task_1.write({
|
|
'display_project_id': self.project_pigs.id
|
|
})
|
|
|
|
self.assertNotEqual(
|
|
child_task_1.partner_id, self.project_pigs.partner_id,
|
|
"When the project changes, the subtask should keep its partner id as its partner id is set.")
|
|
|
|
# restore the partner_id of the 'goats' project
|
|
self.project_goats.write({
|
|
'partner_id': goats_partner_id
|
|
})
|
|
|
|
# set a project with partner_id to a subtask with a project partner_id
|
|
child_task_2.write({
|
|
'display_project_id': self.project_goats.id
|
|
})
|
|
|
|
self.assertEqual(
|
|
child_task_2.partner_id, parent_task.partner_id,
|
|
"When the project changes, the subtask should keep the same partner id even it has a new project.")
|
|
|
|
def test_rating(self):
|
|
"""Check if rating works correctly even when task is changed from project A to project B"""
|
|
Task = self.env['project.task'].with_context({'tracking_disable': True})
|
|
first_task = Task.create({
|
|
'name': 'first task',
|
|
'user_id': self.user_projectuser.id,
|
|
'project_id': self.project_pigs.id,
|
|
'partner_id': self.partner_2.id,
|
|
})
|
|
|
|
self.assertEqual(first_task.rating_count, 0, "Task should have no rating associated with it")
|
|
|
|
rating_good = self.env['rating.rating'].create({
|
|
'res_model_id': self.env['ir.model']._get('project.task').id,
|
|
'res_id': first_task.id,
|
|
'parent_res_model_id': self.env['ir.model']._get('project.project').id,
|
|
'parent_res_id': self.project_pigs.id,
|
|
'rated_partner_id': self.partner_2.id,
|
|
'partner_id': self.partner_2.id,
|
|
'rating': 5,
|
|
'consumed': False,
|
|
})
|
|
|
|
rating_bad = self.env['rating.rating'].create({
|
|
'res_model_id': self.env['ir.model']._get('project.task').id,
|
|
'res_id': first_task.id,
|
|
'parent_res_model_id': self.env['ir.model']._get('project.project').id,
|
|
'parent_res_id': self.project_pigs.id,
|
|
'rated_partner_id': self.partner_2.id,
|
|
'partner_id': self.partner_2.id,
|
|
'rating': 3,
|
|
'consumed': True,
|
|
})
|
|
|
|
# We need to invalidate cache since it is not done automatically by the ORM
|
|
# Our One2Many is linked to a res_id (int) for which the orm doesn't create an inverse
|
|
first_task.invalidate_cache()
|
|
|
|
self.assertEqual(rating_good.rating_text, 'top')
|
|
self.assertEqual(rating_bad.rating_text, 'ok')
|
|
self.assertEqual(first_task.rating_count, 1, "Task should have only one rating associated, since one is not consumed")
|
|
self.assertEqual(rating_good.parent_res_id, self.project_pigs.id)
|
|
|
|
self.assertEqual(self.project_goats.rating_percentage_satisfaction, -1)
|
|
self.assertEqual(self.project_pigs.rating_percentage_satisfaction, 0) # There is a rating but not a "great" on, just an "okay".
|
|
|
|
# Consuming rating_good
|
|
first_task.rating_apply(5, rating_good.access_token)
|
|
|
|
# We need to invalidate cache since it is not done automatically by the ORM
|
|
# Our One2Many is linked to a res_id (int) for which the orm doesn't create an inverse
|
|
first_task.invalidate_cache()
|
|
|
|
self.assertEqual(first_task.rating_count, 2, "Task should have two ratings associated with it")
|
|
self.assertEqual(rating_good.parent_res_id, self.project_pigs.id)
|
|
self.assertEqual(self.project_goats.rating_percentage_satisfaction, -1)
|
|
self.assertEqual(self.project_pigs.rating_percentage_satisfaction, 50)
|
|
|
|
# We change the task from project_pigs to project_goats, ratings should be associated with the new project
|
|
first_task.project_id = self.project_goats.id
|
|
|
|
# We need to invalidate cache since it is not done automatically by the ORM
|
|
# Our One2Many is linked to a res_id (int) for which the orm doesn't create an inverse
|
|
first_task.invalidate_cache()
|
|
|
|
self.assertEqual(rating_good.parent_res_id, self.project_goats.id)
|
|
self.assertEqual(self.project_goats.rating_percentage_satisfaction, 50)
|
|
self.assertEqual(self.project_pigs.rating_percentage_satisfaction, -1)
|
|
|
|
def test_task_with_no_project(self):
|
|
"""
|
|
With this test, we want to make sure the fact that a task has no project doesn't affect the entire
|
|
behaviours of projects.
|
|
|
|
1) Try to compute every field of a task which has no project.
|
|
2) Try to compute every field of a project and assert it isn't affected by this use case.
|
|
"""
|
|
task_without_project = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
|
|
'name': 'Test task without project'
|
|
})
|
|
|
|
for field in task_without_project._fields.keys():
|
|
try:
|
|
task_without_project[field]
|
|
except Exception as e:
|
|
raise AssertionError("Error raised unexpectedly while computing a field of the task ! Exception : " + e.args[0])
|
|
|
|
for field in self.project_pigs._fields.keys():
|
|
try:
|
|
self.project_pigs[field]
|
|
except Exception as e:
|
|
raise AssertionError("Error raised unexpectedly while computing a field of the project ! Exception : " + e.args[0])
|