diff --git a/addons/crm_todo/crm_todo_view.xml b/addons/crm_todo/crm_todo_view.xml
index 851c2abfba4..96d00e76932 100644
--- a/addons/crm_todo/crm_todo_view.xml
+++ b/addons/crm_todo/crm_todo_view.xml
@@ -16,12 +16,8 @@
-
-
-
-
-
-
+
+
diff --git a/addons/pad_project/project_task.xml b/addons/pad_project/project_task.xml
index 24efbc9dad0..c8cbbe1c626 100644
--- a/addons/pad_project/project_task.xml
+++ b/addons/pad_project/project_task.xml
@@ -6,7 +6,7 @@
-
+
diff --git a/addons/portal_project/tests/test_access_rights.py b/addons/portal_project/tests/test_access_rights.py
index a7d56cb3895..1b98a140b68 100644
--- a/addons/portal_project/tests/test_access_rights.py
+++ b/addons/portal_project/tests/test_access_rights.py
@@ -19,35 +19,17 @@
#
##############################################################################
+from openerp.addons.project.tests.test_project_base import TestProjectBase
from openerp.osv.orm import except_orm
-from openerp.tests import common
from openerp.tools import mute_logger
-class TestPortalProject(common.TransactionCase):
+class TestPortalProjectBase(TestProjectBase):
def setUp(self):
- super(TestPortalProject, self).setUp()
+ super(TestPortalProjectBase, self).setUp()
cr, uid = self.cr, self.uid
- # Useful models
- self.project_project = self.registry('project.project')
- self.project_task = self.registry('project.task')
- self.res_users = self.registry('res.users')
- self.res_partner = self.registry('res.partner')
-
- # Find Employee group
- group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
- self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
-
- # Find Project User group
- group_project_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_user')
- self.group_project_user_id = group_project_user_ref and group_project_user_ref[1] or False
-
- # Find Project Manager group
- group_project_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_manager')
- self.group_project_manager_id = group_project_manager_ref and group_project_manager_ref[1] or False
-
# Find Portal group
group_portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal')
self.group_portal_id = group_portal_ref and group_portal_ref[1] or False
@@ -56,62 +38,39 @@ class TestPortalProject(common.TransactionCase):
group_anonymous_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_anonymous')
self.group_anonymous_id = group_anonymous_ref and group_anonymous_ref[1] or False
- # Test users to use through the various tests
- self.user_alfred_id = self.res_users.create(cr, uid, {
- 'name': 'Alfred EmployeeUser',
- 'login': 'alfred',
- 'alias_name': 'alfred',
- 'groups_id': [(6, 0, [self.group_employee_id, self.group_project_user_id])]
- })
- self.user_bert_id = self.res_users.create(cr, uid, {
- 'name': 'Bert Nobody',
- 'login': 'bert',
- 'alias_name': 'bert',
- 'groups_id': [(6, 0, [])]
- })
- self.user_chell_id = self.res_users.create(cr, uid, {
- 'name': 'Chell Portal',
- 'login': 'chell',
- 'alias_name': 'chell',
- 'groups_id': [(6, 0, [self.group_portal_id])]
- })
- self.user_donovan_id = self.res_users.create(cr, uid, {
- 'name': 'Donovan Anonymous',
- 'login': 'donovan',
- 'alias_name': 'donovan',
- 'groups_id': [(6, 0, [self.group_anonymous_id])]
- })
- self.user_ernest_id = self.res_users.create(cr, uid, {
- 'name': 'Ernest Manager',
- 'login': 'ernest',
- 'alias_name': 'ernest',
- 'groups_id': [(6, 0, [self.group_project_manager_id])]
- })
+ # # Test users to use through the various tests
+ self.user_portal_id = self.res_users.create(cr, uid, {
+ 'name': 'Chell Portal',
+ 'login': 'chell',
+ 'alias_name': 'chell',
+ 'groups_id': [(6, 0, [self.group_portal_id])]
+ })
+ self.user_anonymous_id = self.res_users.create(cr, uid, {
+ 'name': 'Donovan Anonymous',
+ 'login': 'donovan',
+ 'alias_name': 'donovan',
+ 'groups_id': [(6, 0, [self.group_anonymous_id])]
+ })
# Test 'Pigs' project
- self.project_pigs_id = self.project_project.create(cr, uid,
- {'name': 'Pigs', 'privacy_visibility': 'public'},
- {'mail_create_nolog': True})
+ self.project_pigs_id = self.project_project.create(cr, uid, {
+ 'name': 'Pigs', 'privacy_visibility': 'public'}, {'mail_create_nolog': True})
# Various test tasks
- self.task_1_id = self.project_task.create(cr, uid,
- {'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.task_2_id = self.project_task.create(cr, uid,
- {'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.task_3_id = self.project_task.create(cr, uid,
- {'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.task_4_id = self.project_task.create(cr, uid,
- {'name': 'Test4', 'user_id': self.user_alfred_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.task_5_id = self.project_task.create(cr, uid,
- {'name': 'Test5', 'user_id': self.user_chell_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.task_6_id = self.project_task.create(cr, uid,
- {'name': 'Test6', 'user_id': self.user_donovan_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
+ self.task_1_id = self.project_task.create(cr, uid, {
+ 'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.task_2_id = self.project_task.create(cr, uid, {
+ 'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.task_3_id = self.project_task.create(cr, uid, {
+ 'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.task_4_id = self.project_task.create(cr, uid, {
+ 'name': 'Test4', 'user_id': self.user_projectuser_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.task_5_id = self.project_task.create(cr, uid, {
+ 'name': 'Test5', 'user_id': self.user_portal_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.task_6_id = self.project_task.create(cr, uid, {
+ 'name': 'Test6', 'user_id': self.user_anonymous_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+
+class TestPortalProject(TestPortalProjectBase):
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_00_project_access_rights(self):
""" Test basic project access rights, for project and portal_project """
@@ -122,53 +81,47 @@ class TestPortalProject(common.TransactionCase):
# ----------------------------------------
# Do: Alfred reads project -> ok (employee ok public)
- self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: all project tasks visible
- task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_2_id, self.task_3_id, self.task_4_id, self.task_5_id, self.task_6_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: project user cannot see all tasks of a public project')
+ 'access rights: project user cannot see all tasks of a public project')
# Test: all project tasks readable
- self.project_task.read(cr, self.user_alfred_id, task_ids, ['name'])
+ self.project_task.read(cr, self.user_projectuser_id, task_ids, ['name'])
# Test: all project tasks writable
- self.project_task.write(cr, self.user_alfred_id, task_ids, {'description': 'TestDescription'})
+ self.project_task.write(cr, self.user_projectuser_id, task_ids, {'description': 'TestDescription'})
# Do: Bert reads project -> crash, no group
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_bert_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Test: no project task visible
- self.assertRaises(except_orm, self.project_task.search,
- cr, self.user_bert_id, [('project_id', '=', pigs_id)])
+ self.assertRaises(except_orm, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Test: no project task readable
- self.assertRaises(except_orm, self.project_task.read,
- cr, self.user_bert_id, task_ids, ['name'])
+ self.assertRaises(except_orm, self.project_task.read, cr, self.user_none_id, task_ids, ['name'])
# Test: no project task writable
- self.assertRaises(except_orm, self.project_task.write,
- cr, self.user_bert_id, task_ids, {'description': 'TestDescription'})
+ self.assertRaises(except_orm, self.project_task.write, cr, self.user_none_id, task_ids, {'description': 'TestDescription'})
# Do: Chell reads project -> ok (portal ok public)
- self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_portal_id, pigs_id, ['name'])
# Test: all project tasks visible
- task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: project user cannot see all tasks of a public project')
+ 'access rights: project user cannot see all tasks of a public project')
# Test: all project tasks readable
- self.project_task.read(cr, self.user_chell_id, task_ids, ['name'])
+ self.project_task.read(cr, self.user_portal_id, task_ids, ['name'])
# Test: no project task writable
- self.assertRaises(except_orm, self.project_task.write,
- cr, self.user_chell_id, task_ids, {'description': 'TestDescription'})
+ self.assertRaises(except_orm, self.project_task.write, cr, self.user_portal_id, task_ids, {'description': 'TestDescription'})
# Do: Donovan reads project -> ok (anonymous ok public)
- self.project_project.read(cr, self.user_donovan_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: all project tasks visible
- task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: anonymous user cannot see all tasks of a public project')
+ 'access rights: anonymous user cannot see all tasks of a public project')
# Test: all project tasks readable
- self.project_task.read(cr, self.user_donovan_id, task_ids, ['name'])
+ self.project_task.read(cr, self.user_anonymous_id, task_ids, ['name'])
# Test: no project task writable
- self.assertRaises(except_orm, self.project_task.write,
- cr, self.user_donovan_id, task_ids, {'description': 'TestDescription'})
+ self.assertRaises(except_orm, self.project_task.write, cr, self.user_anonymous_id, task_ids, {'description': 'TestDescription'})
# ----------------------------------------
# CASE2: portal project
@@ -176,39 +129,36 @@ class TestPortalProject(common.TransactionCase):
self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'portal'})
# Do: Alfred reads project -> ok (employee ok public)
- self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: all project tasks visible
- task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: project user cannot see all tasks of a portal project')
+ 'access rights: project user cannot see all tasks of a portal project')
# Do: Bert reads project -> crash, no group
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_bert_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Test: no project task searchable
- self.assertRaises(except_orm, self.project_task.search,
- cr, self.user_bert_id, [('project_id', '=', pigs_id)])
+ self.assertRaises(except_orm, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Data: task follower
- self.project_task.message_subscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id])
+ self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id])
# Do: Chell reads project -> ok (portal ok public)
- self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_portal_id, pigs_id, ['name'])
# Test: only followed project tasks visible + assigned
- task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_3_id, self.task_5_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: portal user should see the followed tasks of a portal project')
+ 'access rights: portal user should see the followed tasks of a portal project')
# Do: Donovan reads project -> ko (anonymous ko portal)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_donovan_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: no project task visible
- task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of a portal project')
# Data: task follower cleaning
- self.project_task.message_unsubscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id])
+ self.project_task.message_unsubscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id])
# ----------------------------------------
# CASE3: employee project
@@ -216,29 +166,26 @@ class TestPortalProject(common.TransactionCase):
self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'employees'})
# Do: Alfred reads project -> ok (employee ok employee)
- self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: all project tasks visible
- task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_2_id, self.task_3_id, self.task_4_id, self.task_5_id, self.task_6_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: project user cannot see all tasks of an employees project')
+ 'access rights: project user cannot see all tasks of an employees project')
# Do: Bert reads project -> crash, no group
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_bert_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Do: Chell reads project -> ko (portal ko employee)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_chell_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, pigs_id, ['name'])
# Test: no project task visible + assigned
- task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: portal user should not see tasks of an employees project, even if assigned')
# Do: Donovan reads project -> ko (anonymous ko employee)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_donovan_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: no project task visible
- task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of an employees project')
# ----------------------------------------
@@ -247,54 +194,49 @@ class TestPortalProject(common.TransactionCase):
self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'followers'})
# Do: Alfred reads project -> ko (employee ko followers)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_alfred_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: no project task visible
- task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_4_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: employee user should not see tasks of a not-followed followers project, only assigned')
+ 'access rights: employee user should not see tasks of a not-followed followers project, only assigned')
# Do: Bert reads project -> crash, no group
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_bert_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Do: Chell reads project -> ko (portal ko employee)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_chell_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, pigs_id, ['name'])
# Test: no project task visible
- task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_5_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: portal user should not see tasks of a not-followed followers project, only assigned')
+ 'access rights: portal user should not see tasks of a not-followed followers project, only assigned')
# Do: Donovan reads project -> ko (anonymous ko employee)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_donovan_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: no project task visible
- task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of a followers project')
# Data: subscribe Alfred, Chell and Donovan as follower
- self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_alfred_id, self.user_chell_id, self.user_donovan_id])
- self.project_task.message_subscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id, self.user_alfred_id])
+ self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_projectuser_id, self.user_portal_id, self.user_anonymous_id])
+ self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id, self.user_projectuser_id])
# Do: Alfred reads project -> ok (follower ok followers)
- self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: followed + assigned tasks visible
- task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_3_id, self.task_4_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: employee user should not see followed + assigned tasks of a follower project')
+ 'access rights: employee user should not see followed + assigned tasks of a follower project')
# Do: Chell reads project -> ok (follower ok follower)
- self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_portal_id, pigs_id, ['name'])
# Test: followed + assigned tasks visible
- task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_3_id, self.task_5_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: employee user should not see followed + assigned tasks of a follower project')
+ 'access rights: employee user should not see followed + assigned tasks of a follower project')
# Do: Donovan reads project -> ko (anonymous ko follower even if follower)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_donovan_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
diff --git a/addons/portal_project_issue/portal_project_issue_view.xml b/addons/portal_project_issue/portal_project_issue_view.xml
index 2750edf300f..c4f1e0aca57 100644
--- a/addons/portal_project_issue/portal_project_issue_view.xml
+++ b/addons/portal_project_issue/portal_project_issue_view.xml
@@ -26,7 +26,7 @@
diff --git a/addons/portal_project_issue/tests/test_access_rights.py b/addons/portal_project_issue/tests/test_access_rights.py
index 28d337a4965..a7721b489a0 100644
--- a/addons/portal_project_issue/tests/test_access_rights.py
+++ b/addons/portal_project_issue/tests/test_access_rights.py
@@ -19,40 +19,36 @@
#
##############################################################################
-from openerp.addons.portal_project.tests.test_access_rights import TestPortalProject
+from openerp.addons.portal_project.tests.test_access_rights import TestPortalProjectBase
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger
-class TestPortalIssueProject(TestPortalProject):
+class TestPortalProjectBase(TestPortalProjectBase):
def setUp(self):
- super(TestPortalIssueProject, self).setUp()
+ super(TestPortalProjectBase, self).setUp()
cr, uid = self.cr, self.uid
# Useful models
self.project_issue = self.registry('project.issue')
# Various test issues
- self.issue_1_id = self.project_issue.create(cr, uid,
- {'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.issue_2_id = self.project_issue.create(cr, uid,
- {'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.issue_3_id = self.project_issue.create(cr, uid,
- {'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.issue_4_id = self.project_issue.create(cr, uid,
- {'name': 'Test4', 'user_id': self.user_alfred_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.issue_5_id = self.project_issue.create(cr, uid,
- {'name': 'Test5', 'user_id': self.user_chell_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.issue_6_id = self.project_issue.create(cr, uid,
- {'name': 'Test6', 'user_id': self.user_donovan_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
+ self.issue_1_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.issue_2_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.issue_3_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.issue_4_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test4', 'user_id': self.user_projectuser_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.issue_5_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test5', 'user_id': self.user_portal_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.issue_6_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test6', 'user_id': self.user_anonymous_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+
+class TestPortalIssue(TestPortalProjectBase):
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_00_project_access_rights(self):
""" Test basic project access rights, for project and portal_project """
@@ -64,42 +60,38 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ok (employee ok public)
# Test: all project issues visible
- issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_2_id, self.issue_3_id, self.issue_4_id, self.issue_5_id, self.issue_6_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: project user cannot see all issues of a public project')
+ 'access rights: project user cannot see all issues of a public project')
# Test: all project issues readable
- self.project_issue.read(cr, self.user_alfred_id, issue_ids, ['name'])
+ self.project_issue.read(cr, self.user_projectuser_id, issue_ids, ['name'])
# Test: all project issues writable
- self.project_issue.write(cr, self.user_alfred_id, issue_ids, {'description': 'TestDescription'})
+ self.project_issue.write(cr, self.user_projectuser_id, issue_ids, {'description': 'TestDescription'})
# Do: Bert reads project -> crash, no group
# Test: no project issue visible
- self.assertRaises(except_orm, self.project_issue.search,
- cr, self.user_bert_id, [('project_id', '=', pigs_id)])
+ self.assertRaises(except_orm, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Test: no project issue readable
- self.assertRaises(except_orm, self.project_issue.read,
- cr, self.user_bert_id, issue_ids, ['name'])
+ self.assertRaises(except_orm, self.project_issue.read, cr, self.user_none_id, issue_ids, ['name'])
# Test: no project issue writable
- self.assertRaises(except_orm, self.project_issue.write,
- cr, self.user_bert_id, issue_ids, {'description': 'TestDescription'})
+ self.assertRaises(except_orm, self.project_issue.write, cr, self.user_none_id, issue_ids, {'description': 'TestDescription'})
# Do: Chell reads project -> ok (portal ok public)
# Test: all project issues visible
- issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: project user cannot see all issues of a public project')
+ 'access rights: project user cannot see all issues of a public project')
# Test: all project issues readable
- self.project_issue.read(cr, self.user_chell_id, issue_ids, ['name'])
+ self.project_issue.read(cr, self.user_portal_id, issue_ids, ['name'])
# Test: no project issue writable
- self.assertRaises(except_orm, self.project_issue.write,
- cr, self.user_chell_id, issue_ids, {'description': 'TestDescription'})
+ self.assertRaises(except_orm, self.project_issue.write, cr, self.user_portal_id, issue_ids, {'description': 'TestDescription'})
# Do: Donovan reads project -> ok (anonymous ok public)
# Test: all project issues visible
- issue_ids = self.project_issue.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: project user cannot see all issues of a public project')
+ 'access rights: project user cannot see all issues of a public project')
# ----------------------------------------
# CASE2: portal project
@@ -108,27 +100,26 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ok (employee ok public)
# Test: all project issues visible
- issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: project user cannot see all issues of a portal project')
+ 'access rights: project user cannot see all issues of a portal project')
# Do: Bert reads project -> crash, no group
# Test: no project issue searchable
- self.assertRaises(except_orm, self.project_issue.search,
- cr, self.user_bert_id, [('project_id', '=', pigs_id)])
+ self.assertRaises(except_orm, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Data: issue follower
- self.project_issue.message_subscribe_users(cr, self.user_alfred_id, [self.issue_1_id, self.issue_3_id], [self.user_chell_id])
+ self.project_issue.message_subscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id])
# Do: Chell reads project -> ok (portal ok public)
# Test: only followed project issues visible + assigned
- issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_3_id, self.issue_5_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: portal user should see the followed issues of a portal project')
+ 'access rights: portal user should see the followed issues of a portal project')
# Data: issue follower cleaning
- self.project_issue.message_unsubscribe_users(cr, self.user_alfred_id, [self.issue_1_id, self.issue_3_id], [self.user_chell_id])
+ self.project_issue.message_unsubscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id])
# ----------------------------------------
# CASE3: employee project
@@ -137,14 +128,14 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ok (employee ok employee)
# Test: all project issues visible
- issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_2_id, self.issue_3_id, self.issue_4_id, self.issue_5_id, self.issue_6_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: project user cannot see all issues of an employees project')
+ 'access rights: project user cannot see all issues of an employees project')
# Do: Chell reads project -> ko (portal ko employee)
# Test: no project issue visible + assigned
- issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertFalse(issue_ids, 'access rights: portal user should not see issues of an employees project, even if assigned')
# ----------------------------------------
@@ -154,32 +145,32 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ko (employee ko followers)
# Test: no project issue visible
- issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_4_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: employee user should not see issues of a not-followed followers project, only assigned')
+ 'access rights: employee user should not see issues of a not-followed followers project, only assigned')
# Do: Chell reads project -> ko (portal ko employee)
# Test: no project issue visible
- issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_5_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: portal user should not see issues of a not-followed followers project, only assigned')
+ 'access rights: portal user should not see issues of a not-followed followers project, only assigned')
# Data: subscribe Alfred, Chell and Donovan as follower
- self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_alfred_id, self.user_chell_id, self.user_donovan_id])
- self.project_issue.message_subscribe_users(cr, self.user_alfred_id, [self.issue_1_id, self.issue_3_id], [self.user_chell_id, self.user_alfred_id])
+ self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_projectuser_id, self.user_portal_id, self.user_anonymous_id])
+ self.project_issue.message_subscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id, self.user_projectuser_id])
# Do: Alfred reads project -> ok (follower ok followers)
# Test: followed + assigned issues visible
- issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_3_id, self.issue_4_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: employee user should not see followed + assigned issues of a follower project')
+ 'access rights: employee user should not see followed + assigned issues of a follower project')
# Do: Chell reads project -> ok (follower ok follower)
# Test: followed + assigned issues visible
- issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_3_id, self.issue_5_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: employee user should not see followed + assigned issues of a follower project')
+ 'access rights: employee user should not see followed + assigned issues of a follower project')
diff --git a/addons/project/__openerp__.py b/addons/project/__openerp__.py
index c0a516cf555..f5ba24c41cb 100644
--- a/addons/project/__openerp__.py
+++ b/addons/project/__openerp__.py
@@ -40,7 +40,6 @@
],
'depends': [
'base_setup',
- 'base_status',
'product',
'analytic',
'board',
@@ -66,7 +65,6 @@ Dashboard / Reports for Project Management will include:
'data': [
'security/project_security.xml',
'wizard/project_task_delegate_view.xml',
- 'wizard/project_task_reevaluate_view.xml',
'security/ir.model.access.csv',
'project_data.xml',
'project_view.xml',
@@ -79,9 +77,6 @@ Dashboard / Reports for Project Management will include:
],
'demo': ['project_demo.xml'],
'test': [
- 'test/project_demo.yml',
- 'test/project_process.yml',
- 'test/task_process.yml',
],
'installable': True,
'auto_install': False,
diff --git a/addons/project/board_project_view.xml b/addons/project/board_project_view.xml
index 931c661d211..3a14f7b6274 100644
--- a/addons/project/board_project_view.xml
+++ b/addons/project/board_project_view.xml
@@ -16,7 +16,6 @@
-
@@ -26,7 +25,7 @@
project.task
form
tree,form
-
[('user_id','=',uid),('state','not in',('cancel','done'))]
+
[('user_id', '=', uid), ('stage_id.fold', '!=', True)]
diff --git a/addons/project/doc/changelog.rst b/addons/project/doc/changelog.rst
new file mode 100644
index 00000000000..ba5b242f1c0
--- /dev/null
+++ b/addons/project/doc/changelog.rst
@@ -0,0 +1,16 @@
+.. _changelog:
+
+Changelog
+=========
+
+`trunk (saas-2)`
+----------------
+
+- Stage/state update
+
+ - ``project.task``: removed inheritance from ``base_stage`` class and removed
+ ``state`` field. Added ``date_last_stage_update`` field holding last stage_id
+ modification. Updated reports.
+ - ``project.task.type``: removed ``state`` field.
+
+- Removed ``project.task.reevaluate`` wizard.
diff --git a/addons/project/doc/index.rst b/addons/project/doc/index.rst
new file mode 100644
index 00000000000..118b8e7006b
--- /dev/null
+++ b/addons/project/doc/index.rst
@@ -0,0 +1,22 @@
+=====================
+Project DevDoc
+=====================
+
+Project module documentation
+===================================
+
+Documentation topics
+''''''''''''''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ stage_status.rst
+
+Changelog
+'''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ changelog.rst
diff --git a/addons/project/doc/stage_status.rst b/addons/project/doc/stage_status.rst
new file mode 100644
index 00000000000..cc2a64c2fbf
--- /dev/null
+++ b/addons/project/doc/stage_status.rst
@@ -0,0 +1,55 @@
+.. _stage_status:
+
+Stage and Status
+================
+
+.. versionchanged:: 8.0 saas-2 state/stage cleaning
+
+Stage
++++++
+
+This revision removed the concept of state on project.task objects. The ``state``
+field has been totally removed and replaced by stages, using ``stage_id``. The
+following models are impacted:
+
+ - ``project.task`` now use only stages. However a convention still exists about
+ 'New' stage. A task is consdered as ``new`` when it has the following
+ properties:
+
+ - ``stage_id and stage_id.sequence = 1``
+
+ - ``project.task.type`` do not have any ``state`` field anymore.
+ - ``project.task.report`` do not have any ``state`` field anymore.
+
+By default a newly created task is in a new stage. It means that it will
+fetch the stage having ``sequence = 1``. Stage mangement is done using the
+kanban view or the clikable statusbar. It is not done using buttons anymore.
+
+Stage analysis
+++++++++++++++
+
+Stage analysis can be performed using the newly introduced ``date_last_stage_update``
+datetime field. This field is updated everytime ``stage_id`` is updated.
+
+``project.task.report`` model also uses the ``date_last_stage_update`` field.
+This allows to group and analyse the time spend in the various stages.
+
+Open / Assignation date
++++++++++++++++++++++++
+
+The ``date_open`` field meaning has been updated. It is now set when the ``user_id``
+(responsible) is set. It is therefore the assignation date.
+
+Subtypes
+++++++++
+
+The following subtypes are triggered on ``project.task``:
+
+ - ``mt_task_new``: new tasks. Condition: ``obj.stage_id and obj.stage_id.sequence == 1``
+ - ``mt_task_stage``: stage changed. Condition: ``obj.stage_id and obj.stage_id.sequence != 1``
+ - ``mt_task_assigned``: user assigned. condition: ``obj.user_id and obj.user_id.id``
+ - ``mt_task_blocked``: kanban state blocked. Condition: ``obj.kanban_state == 'blocked'``
+
+
+Those subtypes are also available on the ``project.project`` model and are used
+for the auto subscription.
diff --git a/addons/project/project.py b/addons/project/project.py
index 6bd17f98e92..c8b900c9e84 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -25,13 +25,10 @@ import time
from openerp import SUPERUSER_ID
from openerp import tools
+from openerp.addons.resource.faces import task as Task
from openerp.osv import fields, osv
from openerp.tools.translate import _
-from openerp.addons.base_status.base_stage import base_stage
-from openerp.addons.resource.faces import task as Task
-
-_TASK_STATE = [('draft', 'New'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done'), ('cancelled', 'Cancelled')]
class project_task_type(osv.osv):
_name = 'project.task.type'
@@ -44,33 +41,18 @@ class project_task_type(osv.osv):
'case_default': fields.boolean('Default for New Projects',
help="If you check this field, this stage will be proposed by default on each new project. It will not assign this stage to existing projects."),
'project_ids': fields.many2many('project.project', 'project_task_type_rel', 'type_id', 'project_id', 'Projects'),
- 'state': fields.selection(_TASK_STATE, 'Related Status', required=True,
- help="The status of your document is automatically changed regarding the selected stage. " \
- "For example, if a stage is related to the status 'Close', when your document reaches this stage, it is automatically closed."),
'fold': fields.boolean('Folded by Default',
help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
}
- def _get_default_project_id(self, cr, uid, ctx={}):
- proj = ctx.get('default_project_id', False)
- if type(proj) is int:
- return [proj]
- return proj
+
_defaults = {
'sequence': 1,
- 'state': 'open',
'fold': False,
'case_default': False,
- 'project_ids': _get_default_project_id
+ 'project_ids': lambda self, cr, uid, ctx=None: self.pool['project.task']._get_default_project_id(cr, uid, context=ctx),
}
_order = 'sequence'
-def short_name(name):
- """Keep first word(s) of name to make it small enough
- but distinctive"""
- if not name: return name
- # keep 7 chars + end of the last word
- keep_words = name[:7].strip().split()
- return ' '.join(name.split()[:len(keep_words)])
class project(osv.osv):
_name = "project.project"
@@ -99,9 +81,9 @@ class project(osv.osv):
def onchange_partner_id(self, cr, uid, ids, part=False, context=None):
partner_obj = self.pool.get('res.partner')
- if not part:
- return {'value':{}}
val = {}
+ if not part:
+ return {'value': val}
if 'pricelist_id' in self.fields_get(cr, uid, context=context):
pricelist = partner_obj.read(cr, uid, part, ['property_product_pricelist'], context=context)
pricelist_id = pricelist.get('property_product_pricelist', False) and pricelist.get('property_product_pricelist')[0] or False
@@ -152,11 +134,13 @@ class project(osv.osv):
cr.execute("""
SELECT project_id, COALESCE(SUM(planned_hours), 0.0),
COALESCE(SUM(total_hours), 0.0), COALESCE(SUM(effective_hours), 0.0)
- FROM project_task WHERE project_id IN %s AND state <> 'cancelled'
+ FROM project_task
+ LEFT JOIN project_task_type ON project_task.stage_id = project_task_type.id
+ WHERE project_task.project_id IN %s AND project_task_type.fold = False
GROUP BY project_id
""", (tuple(child_parent.keys()),))
# aggregate results into res
- res = dict([(id, {'planned_hours':0.0,'total_hours':0.0,'effective_hours':0.0}) for id in ids])
+ res = dict([(id, {'planned_hours':0.0, 'total_hours':0.0, 'effective_hours':0.0}) for id in ids])
for id, planned, total, effective in cr.fetchall():
# add the values specific to id to all parent projects of id in the result
while id:
@@ -253,22 +237,22 @@ class project(osv.osv):
'planned_hours': fields.function(_progress_rate, multi="progress", string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent', help="Sum of spent hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'total_hours': fields.function(_progress_rate, multi="progress", string='Total Time', help="Sum of total hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'progress_rate': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg", help="Percent of tasks closed according to the total of tasks todo.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ),
'type_ids': fields.many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', 'Tasks Stages', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
@@ -323,26 +307,19 @@ class project(osv.osv):
]
def set_template(self, cr, uid, ids, context=None):
- res = self.setActive(cr, uid, ids, value=False, context=context)
- return res
+ return self.setActive(cr, uid, ids, value=False, context=context)
def set_done(self, cr, uid, ids, context=None):
- task_obj = self.pool.get('project.task')
- task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', 'not in', ('cancelled', 'done'))])
- task_obj.case_close(cr, uid, task_ids, context=context)
- return self.write(cr, uid, ids, {'state':'close'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'close'}, context=context)
def set_cancel(self, cr, uid, ids, context=None):
- task_obj = self.pool.get('project.task')
- task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', '!=', 'done')])
- task_obj.case_cancel(cr, uid, task_ids, context=context)
- return self.write(cr, uid, ids, {'state':'cancelled'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
def set_pending(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state':'pending'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'pending'}, context=context)
def set_open(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state':'open'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'open'}, context=context)
def reset_project(self, cr, uid, ids, context=None):
return self.setActive(cr, uid, ids, value=True, context=context)
@@ -459,8 +436,6 @@ class project(osv.osv):
if project.user_id and (project.user_id.id not in u_ids):
u_ids.append(project.user_id.id)
for task in project.tasks:
- if task.state in ('done','cancelled'):
- continue
if task.user_id and (task.user_id.id not in u_ids):
u_ids.append(task.user_id.id)
calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
@@ -525,7 +500,7 @@ def Project():
for project in projects:
project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,))
for task in project.tasks:
- if task.state in ('done', 'cancelled'):
+ if task.stage_id and task.stage_id.fold:
continue
p = getattr(project_gantt, 'Task_%d' % (task.id,))
@@ -540,16 +515,13 @@ def Project():
}, context=context)
return True
- # ------------------------------------------------
- # OpenChatter methods and notifications
- # ------------------------------------------------
-
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
# Prevent double project creation when 'use_tasks' is checked + alias management
create_context = dict(context, project_creation_in_progress=True,
- alias_model_name=vals.get('alias_model', 'project.task'), alias_parent_model_name=self._name)
+ alias_model_name=vals.get('alias_model', 'project.task'),
+ alias_parent_model_name=self._name)
if vals.get('type', False) not in ('template', 'contract'):
vals['type'] = 'contract'
@@ -566,22 +538,22 @@ def Project():
vals.update(alias_model_id=model_ids[0])
return super(project, self).write(cr, uid, ids, vals, context=context)
-class task(base_stage, osv.osv):
+
+class task(osv.osv):
_name = "project.task"
_description = "Task"
_date_name = "date_start"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
- 'state': {
- 'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj.state in ['new', 'draft'],
- 'project.mt_task_started': lambda self, cr, uid, obj, ctx=None: obj.state == 'open',
- 'project.mt_task_closed': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
- },
'stage_id': {
- 'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj.state not in ['new', 'draft', 'done', 'open'],
+ 'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1,
+ 'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id.sequence != 1,
},
- 'kanban_state': { # kanban state: tracked, but only block subtype
+ 'user_id': {
+ 'project.mt_task_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id,
+ },
+ 'kanban_state': {
'project.mt_task_blocked': lambda self, cr, uid, obj, ctx=None: obj.kanban_state == 'blocked',
},
}
@@ -593,7 +565,7 @@ class task(base_stage, osv.osv):
project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
if project and project.partner_id:
return project.partner_id.id
- return super(task, self)._get_default_partner(cr, uid, context=context)
+ return False
def _get_default_project_id(self, cr, uid, context=None):
""" Gives default section by checking if present in the context """
@@ -602,7 +574,7 @@ class task(base_stage, osv.osv):
def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """
project_id = self._get_default_project_id(cr, uid, context=context)
- return self.stage_find(cr, uid, [], project_id, [('state', '=', 'draft')], context=context)
+ return self.stage_find(cr, uid, [], project_id, [('sequence', '=', '1')], context=context)
def _resolve_project_id_from_context(self, cr, uid, context=None):
""" Returns ID of project based on the value of 'default_project_id'
@@ -679,17 +651,18 @@ class task(base_stage, osv.osv):
res[task.id]['progress'] = 0.0
if (task.remaining_hours + hours.get(task.id, 0.0)):
res[task.id]['progress'] = round(min(100.0 * hours.get(task.id, 0.0) / res[task.id]['total_hours'], 99.99),2)
- if task.state in ('done','cancelled'):
+ # TDE CHECK: if task.state in ('done','cancelled'):
+ if task.stage_id and task.stage_id.fold:
res[task.id]['progress'] = 100.0
return res
def onchange_remaining(self, cr, uid, ids, remaining=0.0, planned=0.0):
if remaining and not planned:
- return {'value':{'planned_hours': remaining}}
+ return {'value': {'planned_hours': remaining}}
return {}
def onchange_planned(self, cr, uid, ids, planned=0.0, effective=0.0):
- return {'value':{'remaining_hours': planned - effective}}
+ return {'value': {'remaining_hours': planned - effective}}
def onchange_project(self, cr, uid, id, project_id, context=None):
if project_id:
@@ -698,6 +671,12 @@ class task(base_stage, osv.osv):
return {'value': {'partner_id': project.partner_id.id}}
return {}
+ def onchange_user_id(self, cr, uid, ids, user_id, context=None):
+ vals = {}
+ if user_id:
+ vals['date_start'] = fields.datetime.now()
+ return {'value': vals}
+
def duplicate_task(self, cr, uid, map_ids, context=None):
for new in map_ids.values():
task = self.browse(cr, uid, new, context)
@@ -755,13 +734,6 @@ class task(base_stage, osv.osv):
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of tasks."),
'stage_id': fields.many2one('project.task.type', 'Stage', track_visibility='onchange',
domain="[('project_ids', '=', project_id)]"),
- 'state': fields.related('stage_id', 'state', type="selection", store=True,
- selection=_TASK_STATE, string="Status", readonly=True,
- help='The status is set to \'Draft\', when a case is created.\
- If the case is in progress the status is set to \'Open\'.\
- When the case is over, the status is set to \'Done\'.\
- If the case needs to be reviewed then the status is \
- set to \'Pending\'.'),
'categ_ids': fields.many2many('project.category', string='Tags'),
'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
track_visibility='onchange',
@@ -774,6 +746,7 @@ class task(base_stage, osv.osv):
'date_start': fields.datetime('Starting Date',select=True),
'date_end': fields.datetime('Ending Date',select=True),
'date_deadline': fields.date('Deadline',select=True),
+ 'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
'project_id': fields.many2one('project.project', 'Project', ondelete='set null', select="1", track_visibility='onchange'),
'parent_ids': fields.many2many('project.task', 'project_task_parent_rel', 'task_id', 'parent_id', 'Parent Tasks'),
'child_ids': fields.many2many('project.task', 'project_task_parent_rel', 'parent_id', 'task_id', 'Delegated Tasks'),
@@ -790,7 +763,7 @@ class task(base_stage, osv.osv):
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
- 'progress': fields.function(_hours_get, string='Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
+ 'progress': fields.function(_hours_get, string='Working Time Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours','state'], 10),
'project.task.work': (_get_task, ['hours'], 10),
@@ -813,6 +786,7 @@ class task(base_stage, osv.osv):
_defaults = {
'stage_id': _get_default_stage_id,
'project_id': _get_default_project_id,
+ 'date_last_stage_update': lambda *a: fields.datetime.now(),
'kanban_state': 'normal',
'priority': '2',
'progress': 0,
@@ -940,7 +914,7 @@ class task(base_stage, osv.osv):
section_ids.append(task.project_id.id)
search_domain = []
if section_ids:
- search_domain = [('|')] * (len(section_ids)-1)
+ search_domain = [('|')] * (len(section_ids) - 1)
for section_id in section_ids:
search_domain.append(('project_ids', '=', section_id))
search_domain += list(domain)
@@ -957,82 +931,10 @@ class task(base_stage, osv.osv):
for task in tasks:
if task.child_ids:
for child in task.child_ids:
- if child.state in ['draft', 'open', 'pending']:
+ if child.stage_id and not child.stage_id.fold:
raise osv.except_osv(_("Warning!"), _("Child task still open.\nPlease cancel or complete child task first."))
return True
- def action_close(self, cr, uid, ids, context=None):
- """ This action closes the task
- """
- task_id = len(ids) and ids[0] or False
- self._check_child_task(cr, uid, ids, context=context)
- if not task_id: return False
- return self.do_close(cr, uid, [task_id], context=context)
-
- def do_close(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_close. """
- return self.case_close(cr, uid, ids, context=context)
-
- def case_close(self, cr, uid, ids, context=None):
- """ Closes Task """
- if not isinstance(ids, list): ids = [ids]
- for task in self.browse(cr, uid, ids, context=context):
- vals = {}
- project = task.project_id
- for parent_id in task.parent_ids:
- if parent_id.state in ('pending','draft'):
- reopen = True
- for child in parent_id.child_ids:
- if child.id != task.id and child.state not in ('done','cancelled'):
- reopen = False
- if reopen:
- self.do_reopen(cr, uid, [parent_id.id], context=context)
- # close task
- vals['remaining_hours'] = 0.0
- if not task.date_end:
- vals['date_end'] = fields.datetime.now()
- self.case_set(cr, uid, [task.id], 'done', vals, context=context)
- return True
-
- def do_reopen(self, cr, uid, ids, context=None):
- for task in self.browse(cr, uid, ids, context=context):
- project = task.project_id
- self.case_set(cr, uid, [task.id], 'open', {}, context=context)
- return True
-
- def do_cancel(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_cancel. """
- return self.case_cancel(cr, uid, ids, context=context)
-
- def case_cancel(self, cr, uid, ids, context=None):
- tasks = self.browse(cr, uid, ids, context=context)
- self._check_child_task(cr, uid, ids, context=context)
- for task in tasks:
- self.case_set(cr, uid, [task.id], 'cancelled', {'remaining_hours': 0.0}, context=context)
- return True
-
- def do_open(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_open. """
- return self.case_open(cr, uid, ids, context=context)
-
- def case_open(self, cr, uid, ids, context=None):
- if not isinstance(ids,list): ids = [ids]
- return self.case_set(cr, uid, ids, 'open', {'date_start': fields.datetime.now()}, context=context)
-
- def do_draft(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_draft. """
- return self.case_draft(cr, uid, ids, context=context)
-
- def case_draft(self, cr, uid, ids, context=None):
- return self.case_set(cr, uid, ids, 'draft', {}, context=context)
-
- def do_pending(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_pending. """
- return self.case_pending(cr, uid, ids, context=context)
-
- def case_pending(self, cr, uid, ids, context=None):
- return self.case_set(cr, uid, ids, 'pending', {}, context=context)
-
def _delegate_task_attachments(self, cr, uid, task_id, delegated_task_id, context=None):
attachment = self.pool.get('ir.attachment')
attachment_ids = attachment.search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', task_id)], context=context)
@@ -1053,6 +955,7 @@ class task(base_stage, osv.osv):
delegated_task_id = self.copy(cr, uid, task.id, {
'name': delegate_data['name'],
'project_id': delegate_data['project_id'] and delegate_data['project_id'][0] or False,
+ 'stage_id': delegate_data.get('stage_id') and delegate_data.get('stage_id')[0] or False,
'user_id': delegate_data['user_id'] and delegate_data['user_id'][0] or False,
'planned_hours': delegate_data['planned_hours'] or 0.0,
'parent_ids': [(6, 0, [task.id])],
@@ -1067,16 +970,12 @@ class task(base_stage, osv.osv):
'planned_hours': delegate_data['planned_hours_me'] + (task.effective_hours or 0.0),
'name': newname,
}, context=context)
- if delegate_data['state'] == 'pending':
- self.do_pending(cr, uid, [task.id], context=context)
- elif delegate_data['state'] == 'done':
- self.do_close(cr, uid, [task.id], context=context)
delegated_tasks[task.id] = delegated_task_id
return delegated_tasks
def set_remaining_time(self, cr, uid, ids, remaining_time=1.0, context=None):
for task in self.browse(cr, uid, ids, context=context):
- if (task.state=='draft') or (task.planned_hours==0.0):
+ if (task.stage_id and task.stage_id.sequence == 1) or (task.planned_hours == 0.0):
self.write(cr, uid, [task.id], {'planned_hours': remaining_time}, context=context)
self.write(cr, uid, ids, {'remaining_hours': remaining_time}, context=context)
return True
@@ -1111,17 +1010,25 @@ class task(base_stage, osv.osv):
'planned_hours': task.planned_hours,
'kanban_state': task.kanban_state,
'type_id': task.stage_id.id,
- 'state': task.state,
'user_id': task.user_id.id
}, context=context)
return True
+ # ------------------------------------------------
+ # CRUD overrides
+ # ------------------------------------------------
+
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
+
+ # for default stage
if vals.get('project_id') and not context.get('default_project_id'):
context['default_project_id'] = vals.get('project_id')
+ # user_id change: update date_start
+ if vals.get('user_id'):
+ vals['date_start'] = fields.datetime.now()
# context: no_log, because subtype already handle this
create_context = dict(context, mail_create_nolog=True)
@@ -1129,25 +1036,30 @@ class task(base_stage, osv.osv):
self._store_history(cr, uid, [task_id], context=context)
return task_id
- # Overridden to reset the kanban_state to normal whenever
- # the stage (stage_id) of the task changes.
def write(self, cr, uid, ids, vals, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
+
+ # stage change: update date_last_stage_update
+ if 'stage_id' in vals:
+ vals['date_last_stage_update'] = fields.datetime.now()
+ # user_id change: update date_start
+ if vals.get('user_id'):
+ vals['date_start'] = fields.datetime.now()
+
+ # Overridden to reset the kanban_state to normal whenever
+ # the stage (stage_id) of the task changes.
if vals and not 'kanban_state' in vals and 'stage_id' in vals:
new_stage = vals.get('stage_id')
vals_reset_kstate = dict(vals, kanban_state='normal')
for t in self.browse(cr, uid, ids, context=context):
- #TO FIX:Kanban view doesn't raise warning
- #stages = [stage.id for stage in t.project_id.type_ids]
- #if new_stage not in stages:
- #raise osv.except_osv(_('Warning!'), _('Stage is not defined in the project.'))
write_vals = vals_reset_kstate if t.stage_id != new_stage else vals
super(task, self).write(cr, uid, [t.id], write_vals, context=context)
result = True
else:
result = super(task, self).write(cr, uid, ids, vals, context=context)
- if ('stage_id' in vals) or ('remaining_hours' in vals) or ('user_id' in vals) or ('state' in vals) or ('kanban_state' in vals):
+
+ if any(item in vals for item in ['stage_id', 'remaining_hours', 'user_id', 'kanban_state']):
self._store_history(cr, uid, ids, context=context)
return result
@@ -1163,7 +1075,7 @@ class task(base_stage, osv.osv):
result = ""
ident = ' '*ident
for task in tasks:
- if task.state in ('done','cancelled'):
+ if task.stage_id and task.stage_id.fold:
continue
result += '''
%sdef Task_%s():
@@ -1233,24 +1145,10 @@ class task(base_stage, osv.osv):
update_vals[field] = float(res.group(2).lower())
except (ValueError, TypeError):
pass
- elif match.lower() == 'state' \
- and res.group(2).lower() in ['cancel','close','draft','open','pending']:
- act = 'do_%s' % res.group(2).lower()
if act:
getattr(self,act)(cr, uid, ids, context=context)
return super(task,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
- def project_task_reevaluate(self, cr, uid, ids, context=None):
- if self.pool.get('res.users').has_group(cr, uid, 'project.group_time_work_estimation_tasks'):
- return {
- 'view_type': 'form',
- "view_mode": 'form',
- 'res_model': 'project.task.reevaluate',
- 'type': 'ir.actions.act_window',
- 'target': 'new',
- }
- return self.do_reopen(cr, uid, ids, context=context)
-
class project_work(osv.osv):
_name = "project.task.work"
_description = "Project Task Work"
@@ -1371,7 +1269,7 @@ class project_task_history(osv.osv):
def _get_date(self, cr, uid, ids, name, arg, context=None):
result = {}
for history in self.browse(cr, uid, ids, context=context):
- if history.state in ('done','cancelled'):
+ if history.type_id and history.type_id.fold:
result[history.id] = history.date
continue
cr.execute('''select
@@ -1405,14 +1303,13 @@ class project_task_history(osv.osv):
_columns = {
'task_id': fields.many2one('project.task', 'Task', ondelete='cascade', required=True, select=True),
'type_id': fields.many2one('project.task.type', 'Stage'),
- 'state': fields.selection([('draft', 'New'), ('cancelled', 'Cancelled'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done')], 'Status'),
- 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State', required=False),
+ 'kanban_state': fields.selection([('normal', 'Normal'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')], 'Kanban State', required=False),
'date': fields.date('Date', select=True),
'end_date': fields.function(_get_date, string='End Date', type="date", store={
'project.task.history': (_get_related_date, None, 20)
}),
- 'remaining_hours': fields.float('Remaining Time', digits=(16,2)),
- 'planned_hours': fields.float('Planned Time', digits=(16,2)),
+ 'remaining_hours': fields.float('Remaining Time', digits=(16, 2)),
+ 'planned_hours': fields.float('Planned Time', digits=(16, 2)),
'user_id': fields.many2one('res.users', 'Responsible'),
}
_defaults = {
@@ -1442,7 +1339,7 @@ class project_task_history_cumulative(osv.osv):
SELECT
h.id AS history_id,
h.date+generate_series(0, CAST((coalesce(h.end_date, DATE 'tomorrow')::date - h.date) AS integer)-1) AS date,
- h.task_id, h.type_id, h.user_id, h.kanban_state, h.state,
+ h.task_id, h.type_id, h.user_id, h.kanban_state,
greatest(h.remaining_hours, 1) AS remaining_hours, greatest(h.planned_hours, 1) AS planned_hours,
t.project_id
FROM
diff --git a/addons/project/project_data.xml b/addons/project/project_data.xml
index 7bf1a623a2c..0f48dfe9a2b 100644
--- a/addons/project/project_data.xml
+++ b/addons/project/project_data.xml
@@ -30,51 +30,43 @@
1
Analysis
- draft
-
+
- 2
+ 10
Specification
- pending
- 2
+ 11
Design
- open
- 3
+ 12
Development
- open
- 4
+ 13
Testing
- open
- 5
+ 14
Merge
- open
- 100
+ 20
Done
- done
- 200
+ 30
Cancelled
- cancelled
@@ -86,11 +78,11 @@
Task created
-
- Task Started
+
+ Task Assigned
project.task
- Task started
+ Task Assigned
Task Blocked
@@ -98,12 +90,6 @@
Task blocked
-
- Task Done
- project.task
-
- Task closed
-
Stage Changed
project.task
@@ -118,11 +104,11 @@
project_id
-
- Task Started
+
+ Task Assigned
project.project
-
+
project_id
@@ -131,12 +117,6 @@
project_id
-
- Task Done
- project.project
-
- project_id
-
Task Stage Changed
project.project
diff --git a/addons/project/project_demo.xml b/addons/project/project_demo.xml
index b7e1524c2c8..12e373642fd 100644
--- a/addons/project/project_demo.xml
+++ b/addons/project/project_demo.xml
@@ -225,7 +225,6 @@
ref('project.project_category_04')])]"/>
-
@@ -237,7 +236,6 @@
6
-
@@ -248,7 +246,6 @@
Design Use Cases
-
@@ -282,7 +279,6 @@
Set target for all deparments
-
@@ -293,7 +289,6 @@
Integration of core components
-
@@ -315,7 +310,6 @@
-
42.0
@@ -325,7 +319,6 @@
Create new components
-
14.0
@@ -337,7 +330,6 @@
-
12.0
@@ -371,7 +363,6 @@
-
20
@@ -382,7 +373,6 @@
Data importation + Doc
-
20
diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml
index 33842a9a19c..fd44353b177 100644
--- a/addons/project/project_view.xml
+++ b/addons/project/project_view.xml
@@ -18,29 +18,26 @@
+
+
-
+
-
-
-
-
-
-
-
-
+
+ help="Show only tasks having a deadline"/>
+
+
-
+
@@ -371,18 +368,6 @@
-
+
-
@@ -467,6 +449,7 @@
+
@@ -493,7 +476,6 @@
-
@@ -513,7 +495,6 @@
2
5
10
- !
@@ -562,7 +543,7 @@
project.task
-
+
@@ -575,7 +556,6 @@
-
@@ -661,7 +641,7 @@
project.task
form
tree,form,calendar,graph,kanban
- [('date_deadline','<',time.strftime('%Y-%m-%d')),('state','in',('draft','pending','open'))]
+ [('date_deadline','<',time.strftime('%Y-%m-%d'))]
@@ -706,7 +686,6 @@
-
@@ -723,7 +702,7 @@
-
+
@@ -767,7 +746,9 @@
-
+
diff --git a/addons/project/report/project_cumulative.xml b/addons/project/report/project_cumulative.xml
index 17e059c8de7..9b9490f0b28 100644
--- a/addons/project/report/project_cumulative.xml
+++ b/addons/project/report/project_cumulative.xml
@@ -14,7 +14,6 @@
-
@@ -40,8 +39,7 @@
-
-
+
diff --git a/addons/project/report/project_report.py b/addons/project/report/project_report.py
index 006aae1085c..15b26507ff3 100644
--- a/addons/project/report/project_report.py
+++ b/addons/project/report/project_report.py
@@ -19,9 +19,10 @@
#
##############################################################################
-from openerp.osv import fields,osv
+from openerp.osv import fields, osv
from openerp import tools
+
class report_project_task_user(osv.osv):
_name = "report.project.task.user"
_description = "Tasks by user and project"
@@ -31,10 +32,11 @@ class report_project_task_user(osv.osv):
'day': fields.char('Day', size=128, readonly=True),
'year': fields.char('Year', size=64, required=False, readonly=True),
'user_id': fields.many2one('res.users', 'Assigned To', readonly=True),
- 'date_start': fields.date('Starting Date',readonly=True),
+ 'date_start': fields.date('Assignation Date', readonly=True),
'no_of_days': fields.integer('# of Days', size=128, readonly=True),
'date_end': fields.date('Ending Date', readonly=True),
'date_deadline': fields.date('Deadline', readonly=True),
+ 'date_last_stage_update': fields.date('Last Stage Update', readonly=True),
'project_id': fields.many2one('project.project', 'Project', readonly=True),
'hours_planned': fields.float('Planned Hours', readonly=True),
'hours_effective': fields.float('Effective Hours', readonly=True),
@@ -44,16 +46,17 @@ class report_project_task_user(osv.osv):
'total_hours': fields.float('Total Hours', readonly=True),
'closing_days': fields.float('Days to Close', digits=(16,2), readonly=True, group_operator="avg",
help="Number of Days to close the task"),
- 'opening_days': fields.float('Days to Open', digits=(16,2), readonly=True, group_operator="avg",
+ 'opening_days': fields.float('Days to Assign', digits=(16,2), readonly=True, group_operator="avg",
help="Number of Days to Open the task"),
'delay_endings_days': fields.float('Overpassed Deadline', digits=(16,2), readonly=True),
'nbr': fields.integer('# of tasks', readonly=True),
- 'priority' : fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Urgent'),
-('0','Very urgent')], 'Priority', readonly=True),
- 'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')], 'Month', readonly=True),
+ 'priority': fields.selection([('4', 'Very Low'), ('3', 'Low'), ('2', 'Medium'), ('1', 'Urgent'), ('0', 'Very urgent')],
+ string='Priority', readonly=True),
+ 'month':fields.selection(fields.date.MONTHS, 'Month', readonly=True),
'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')],'Status', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'partner_id': fields.many2one('res.partner', 'Contact', readonly=True),
+ 'stage_id': fields.many2one('project.task.type', 'Stage'),
}
_order = 'name desc, project_id'
@@ -69,19 +72,19 @@ class report_project_task_user(osv.osv):
to_char(date_start, 'YYYY-MM-DD') as day,
date_trunc('day',t.date_start) as date_start,
date_trunc('day',t.date_end) as date_end,
+ date_trunc('day',t.date_last_stage_update) as date_last_stage_update,
to_date(to_char(t.date_deadline, 'dd-MM-YYYY'),'dd-MM-YYYY') as date_deadline,
-- sum(cast(to_char(date_trunc('day',t.date_end) - date_trunc('day',t.date_start),'DD') as int)) as no_of_days,
abs((extract('epoch' from (t.date_end-t.date_start)))/(3600*24)) as no_of_days,
t.user_id,
progress as progress,
t.project_id,
- t.state,
t.effective_hours as hours_effective,
t.priority,
t.name as name,
t.company_id,
t.partner_id,
- t.stage_id,
+ t.stage_id as stage_id,
remaining_hours as remaining_hours,
total_hours as total_hours,
t.delay_hours as hours_delay,
@@ -106,15 +109,12 @@ class report_project_task_user(osv.osv):
date_start,
date_end,
date_deadline,
+ date_last_stage_update,
t.user_id,
t.project_id,
- t.state,
t.priority,
name,
t.company_id,
t.partner_id,
- t.stage_id
-
+ stage_id
""")
-
-
diff --git a/addons/project/report/project_report_view.xml b/addons/project/report/project_report_view.xml
index d9650e165ce..d5638e332a4 100644
--- a/addons/project/report/project_report_view.xml
+++ b/addons/project/report/project_report_view.xml
@@ -13,11 +13,12 @@
+
-
+
@@ -44,7 +45,7 @@
-
+
@@ -58,31 +59,29 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv
index a76faa0fcce..c88fb276fcb 100644
--- a/addons/project/security/ir.model.access.csv
+++ b/addons/project/security/ir.model.access.csv
@@ -19,6 +19,7 @@ access_project_task_history,project.task.history project,project.model_project_t
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_manager,1,0,0,0
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_user,1,0,0,0
access_resource_calendar,project.resource_calendar manager,resource.model_resource_calendar,project.group_project_manager,1,0,0,0
+access_resource_calendar_leaves_user,resource.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1
access_project_category,project.project_category,model_project_category,,1,0,0,0
access_project_category_manager,project.project_category,model_project_category,project.group_project_manager,1,1,1,1
access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1
diff --git a/addons/project/test/project_demo.yml b/addons/project/test/project_demo.yml
deleted file mode 100644
index 5f6acbb913e..00000000000
--- a/addons/project/test/project_demo.yml
+++ /dev/null
@@ -1,12 +0,0 @@
--
- !record {model: project.project, id: project_project_1, view: False}:
- partner_id: base.res_partner_2
--
- !record {model: project.task, id: project_task_1, view: False}:
- remaining_hours: 10.00
--
- !record {model: project.task, id: project_task_1, view: False}:
- planned_hours: 10.00
--
- !record {model: project.task, id: project_task_1, view: False}:
- project_id: project_project_1
\ No newline at end of file
diff --git a/addons/project/test/project_process.yml b/addons/project/test/project_process.yml
deleted file mode 100644
index c2a5c072e9d..00000000000
--- a/addons/project/test/project_process.yml
+++ /dev/null
@@ -1,70 +0,0 @@
--
- In order to Test Process of Project Management,
--
- I create duplicate template.
--
- !python {model: project.project}: |
- new_template = self.duplicate_template(cr, uid, [ref("project_project_1")])
- assert new_template, "duplicate template is not created"
- template = self.browse(cr, uid, new_template['res_id'], context=context)
- assert template.state == 'open', "Duplicate template must be in open state."
--
- I convert template into real Project.
--
- !python {model: project.project}: |
- self.reset_project(cr, uid, [ref("project_project_1")])
--
- I check project details after convert from template.
--
- !assert {model: project.project, id: project_project_1, severity: error, string: Project should be active}:
- - state == "open"
--
- I put project in pending.
--
- !python {model: project.project}: |
- self.set_pending(cr, uid, [ref("project_project_1")])
--
- I check state after put in pending.
--
- !assert {model: project.project, id: project_project_1, severity: error, string: Project should be in pending state}:
- - state == "pending"
--
- I re-open the project.
--
- !python {model: project.project}: |
- self.set_open(cr, uid, [ref("project_project_1")])
--
- I check state after reopen.
--
- !assert {model: project.project, id: project_project_1, severity: error, string: Project should be open.}:
- - state == "open"
--
- I close the project.
--
- !python {model: project.project}: |
- self.set_done(cr, uid, [ref("project_project_1")])
--
- I check state after closed.
--
- !assert {model: project.project, id: project_project_1, severity: error, string: Project should be close.}:
- - state == "close"
--
- I set project into template.
--
- !python {model: project.project}: |
- self.set_template(cr, uid, [ref("project_project_1")])
--
- I schedule tasks of project.
--
- !python {model: project.project}: |
- self.schedule_tasks(cr, uid, [ref("project_project_1")], context=context)
--
- I copy the tasks of project.
--
- !python {model: project.project}: |
- self.copy(cr, uid, ref("project_project_1"))
--
- I cancel Project.
--
- !python {model: project.project}: |
- self.set_cancel(cr, uid, [ref("project_project_2")])
diff --git a/addons/project/test/task_process.yml b/addons/project/test/task_process.yml
deleted file mode 100644
index e37b2116714..00000000000
--- a/addons/project/test/task_process.yml
+++ /dev/null
@@ -1,76 +0,0 @@
--
- I put task in pending due to specification is not clear.
--
- !python {model: project.task}: |
- self.do_pending(cr, uid, [ref("project_task_1")])
- context.update({"active_id": ref("project_task_1")})
--
- I check state of task after put in pending.
--
- !assert {model: project.task, id: project_task_1, severity: error, string: task should be in pending state}:
- - state == "pending"
--
- !record {model: project.task.delegate, id: delegate_id}:
- user_id: base.user_demo
- planned_hours: 12.0
- planned_hours_me: 2.0
--
- Now I delegate task to team member.
--
- !python {model: project.task.delegate}: |
- self.delegate(cr, uid, [ref("delegate_id")], {"active_id": ref("project_task_1")})
--
- I check delegated task details.
--
- !python {model: project.task}: |
- task = self.browse(cr, uid, ref("project_task_1"), context=context)
- assert task.planned_hours == 2.0, "Planning hours is not correct after delegated."
- assert task.state == "pending", "Task should be in Pending after delegated."
--
- I re-open the task.
--
- !python {model: project.task}: |
- self.do_reopen(cr, uid, [ref("project_task_1")])
--
- I check reopened task details.
--
- !assert {model: project.task, id: project_task_1, severity: error, string: task should be open.}:
- - state == "open"
--
- I change the stage of task to next stage.
--
- !python {model: project.task}: |
- self.stage_next(cr, uid, [ref("project_task_1")])
--
- !record {model: project.task.reevaluate, id: reevaluate_id}:
- remaining_hours : 120
--
- I reevaluate task with remaining hours.
--
- !python {model: project.task.reevaluate}: |
- self.compute_hours(cr, uid, [ref("reevaluate_id")], {"active_id": ref("project_task_1")})
--
- I check remaining hours after reevaluated task.
--
- !assert {model: project.task, id: project_task_1, severity: error, string: task should be reevaluated}:
- - remaining_hours == 120.0
--
- I close the task.
--
- !python {model: project.task}: |
- self.action_close(cr, uid, [ref("project_task_1")])
--
- I check state after closed.
--
- !assert {model: project.task, id: project_task_1, severity: error, string: task is in open state}:
- - state == "done"
--
- I change the stage of task to previous stage.
--
- !python {model: project.task}: |
- self.stage_previous(cr, uid, [ref("project_task_1")])
--
- I cancel Task.
--
- !python {model: project.task}: |
- self.do_cancel(cr, uid, [ref("project_task_2")])
diff --git a/addons/project/tests/__init__.py b/addons/project/tests/__init__.py
new file mode 100644
index 00000000000..4d3163d6dc6
--- /dev/null
+++ b/addons/project/tests/__init__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from . import test_project_flow
+
+checks = [
+ test_project_flow,
+]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project/tests/test_project_base.py b/addons/project/tests/test_project_base.py
new file mode 100644
index 00000000000..f82561766fe
--- /dev/null
+++ b/addons/project/tests/test_project_base.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.addons.mail.tests.test_mail_base import TestMailBase
+
+
+class TestProjectBase(TestMailBase):
+
+ def setUp(self):
+ super(TestProjectBase, self).setUp()
+ cr, uid = self.cr, self.uid
+
+ # Usefull models
+ self.project_project = self.registry('project.project')
+ self.project_task = self.registry('project.task')
+ self.project_task_delegate = self.registry('project.task.delegate')
+
+ # Find Project User group
+ group_project_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_user')
+ self.group_project_user_id = group_project_user_ref and group_project_user_ref[1] or False
+
+ # Find Project Manager group
+ group_project_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_manager')
+ self.group_project_manager_id = group_project_manager_ref and group_project_manager_ref[1] or False
+
+ # Test partners to use through the various tests
+ self.project_partner_id = self.res_partner.create(cr, uid, {
+ 'name': 'Gertrude AgrolaitPartner',
+ 'email': 'gertrude.partner@agrolait.com',
+ })
+ self.email_partner_id = self.res_partner.create(cr, uid, {
+ 'name': 'Patrick Ratatouille',
+ 'email': 'patrick.ratatouille@agrolait.com',
+ })
+
+ # Test users to use through the various tests
+ self.user_projectuser_id = self.res_users.create(cr, uid, {
+ 'name': 'Armande ProjectUser',
+ 'login': 'Armande',
+ 'alias_name': 'armande',
+ 'email': 'armande.projectuser@example.com',
+ 'groups_id': [(6, 0, [self.group_employee_id, self.group_project_user_id])]
+ })
+ self.user_projectmanager_id = self.res_users.create(cr, uid, {
+ 'name': 'Bastien ProjectManager',
+ 'login': 'bastien',
+ 'alias_name': 'bastien',
+ 'email': 'bastien.projectmanager@example.com',
+ 'groups_id': [(6, 0, [self.group_employee_id, self.group_project_manager_id])]
+ })
+ self.user_none_id = self.res_users.create(cr, uid, {
+ 'name': 'Charlie Avotbonkeur',
+ 'login': 'charlie',
+ 'alias_name': 'charlie',
+ 'email': 'charlie.noone@example.com',
+ 'groups_id': [(6, 0, [])]
+ })
+ self.user_projectuser = self.res_users.browse(cr, uid, self.user_projectuser_id)
+ self.user_projectmanager = self.res_users.browse(cr, uid, self.user_projectmanager_id)
+ self.partner_projectuser_id = self.user_projectuser.partner_id.id
+ self.partner_projectmanager_id = self.user_projectmanager.partner_id.id
+
+ # Test 'Pigs' project
+ self.project_pigs_id = self.project_project.create(cr, uid, {
+ 'name': 'Pigs',
+ 'privacy_visibility': 'public',
+ 'alias_name': 'project+pigs',
+ 'partner_id': self.partner_raoul_id,
+ }, {'mail_create_nolog': True})
+
+ # Already-existing tasks in Pigs
+ self.task_1_id = self.project_task.create(cr, uid, {
+ 'name': 'Pigs UserTask',
+ 'user_id': self.user_projectuser_id,
+ 'project_id': self.project_pigs_id,
+ }, {'mail_create_nolog': True})
+ self.task_2_id = self.project_task.create(cr, uid, {
+ 'name': 'Pigs ManagerTask',
+ 'user_id': self.user_projectmanager_id,
+ 'project_id': self.project_pigs_id,
+ }, {'mail_create_nolog': True})
diff --git a/addons/project/tests/test_project_flow.py b/addons/project/tests/test_project_flow.py
new file mode 100644
index 00000000000..a037c92d2ae
--- /dev/null
+++ b/addons/project/tests/test_project_flow.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.addons.project.tests.test_project_base import TestProjectBase
+from openerp.osv.orm import except_orm
+from openerp.tools import mute_logger
+
+
+EMAIL_TPL = """Return-Path:
+X-Original-To: {email_to}
+Delivered-To: {email_to}
+To: {email_to}
+Received: by mail1.openerp.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(TestProjectBase):
+
+ @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
+ def test_00_project_process(self):
+ """ Testing project management """
+ cr, uid, user_projectuser_id, user_projectmanager_id, project_pigs_id = self.cr, self.uid, self.user_projectuser_id, self.user_projectmanager_id, self.project_pigs_id
+
+ # ProjectUser: set project as template -> raise
+ self.assertRaises(except_orm, self.project_project.set_template, cr, user_projectuser_id, [project_pigs_id])
+
+ # Other tests are done using a ProjectManager
+ project = self.project_project.browse(cr, user_projectmanager_id, project_pigs_id)
+ self.assertNotEqual(project.state, 'template', 'project: incorrect state, should not be a template')
+
+ # Set test project as template
+ self.project_project.set_template(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+ self.assertEqual(project.state, 'template', 'project: set_template: project state should be template')
+ self.assertEqual(len(project.tasks), 0, 'project: set_template: project tasks should have been set inactive')
+
+ # Duplicate template
+ new_template_act = self.project_project.duplicate_template(cr, user_projectmanager_id, [project_pigs_id])
+ new_project = self.project_project.browse(cr, user_projectmanager_id, new_template_act['res_id'])
+ self.assertEqual(new_project.state, 'open', 'project: incorrect duplicate_template')
+ self.assertEqual(len(new_project.tasks), 2, 'project: duplicating a project template should duplicate its tasks')
+
+ # Convert into real project
+ self.project_project.reset_project(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+ self.assertEqual(project.state, 'open', 'project: resetted project should be in open state')
+ self.assertEqual(len(project.tasks), 2, 'project: reset_project: project tasks should have been set active')
+
+ # Put as pending
+ self.project_project.set_pending(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+ self.assertEqual(project.state, 'pending', 'project: should be in pending state')
+
+ # Re-open
+ self.project_project.set_open(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+ self.assertEqual(project.state, 'open', 'project: reopened project should be in open state')
+
+ # Close project
+ self.project_project.set_done(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+ self.assertEqual(project.state, 'close', 'project: closed project should be in close state')
+
+ # Re-open
+ self.project_project.set_open(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+
+ # Re-convert into a template and schedule tasks
+ self.project_project.set_template(cr, user_projectmanager_id, [project_pigs_id])
+ self.project_project.schedule_tasks(cr, user_projectmanager_id, [project_pigs_id])
+
+ # Copy the project
+ new_project_id = self.project_project.copy(cr, user_projectmanager_id, project_pigs_id)
+ new_project = self.project_project.browse(cr, user_projectmanager_id, new_project_id)
+ self.assertEqual(len(new_project.tasks), 2, 'project: copied project should have copied task')
+
+ # Cancel the project
+ self.project_project.set_cancel(cr, user_projectmanager_id, [project_pigs_id])
+ self.assertEqual(project.state, 'cancelled', 'project: cancelled project should be in cancel state')
+
+ def test_10_task_process(self):
+ """ Testing task creation and management """
+ cr, uid, user_projectuser_id, user_projectmanager_id, project_pigs_id = self.cr, self.uid, self.user_projectuser_id, self.user_projectmanager_id, self.project_pigs_id
+
+ def format_and_process(template, email_to='project+pigs@mydomain.com, other@gmail.com', subject='Frogs',
+ email_from='Patrick Ratatouille ',
+ msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'):
+ self.assertEqual(self.project_task.search(cr, uid, [('name', '=', subject)]), [])
+ mail = template.format(email_to=email_to, subject=subject, email_from=email_from, msg_id=msg_id)
+ self.mail_thread.message_process(cr, uid, None, mail)
+ return self.project_task.search(cr, uid, [('name', '=', subject)])
+
+ # Do: incoming mail from an unknown partner on an alias creates a new task 'Frogs'
+ frogs = format_and_process(EMAIL_TPL)
+
+ # Test: one task created by mailgateway administrator
+ self.assertEqual(len(frogs), 1, 'project: message_process: a new project.task should have been created')
+ task = self.project_task.browse(cr, user_projectuser_id, frogs[0])
+ res = self.project_task.perm_read(cr, uid, [task.id], details=False)
+ self.assertEqual(res[0].get('create_uid'), uid,
+ 'project: message_process: task should have been created by uid as alias_user_id is False on the alias')
+ # Test: messages
+ self.assertEqual(len(task.message_ids), 3,
+ 'project: message_process: newly created task should have 2 messages: creation and email')
+ self.assertEqual(task.message_ids[2].subtype_id.name, 'Task Created',
+ 'project: message_process: first message of new task should have Task Created subtype')
+ self.assertEqual(task.message_ids[1].subtype_id.name, 'Task Assigned',
+ 'project: message_process: first message of new task should have Task Created subtype')
+ self.assertEqual(task.message_ids[0].author_id.id, self.email_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, 1, 'project_task: should have a stage with sequence=1')
+
+ # Open the delegation wizard
+ delegate_id = self.project_task_delegate.create(cr, user_projectuser_id, {
+ 'user_id': user_projectuser_id,
+ 'planned_hours': 12.0,
+ 'planned_hours_me': 2.0,
+ }, {'active_id': task.id})
+ self.project_task_delegate.delegate(cr, user_projectuser_id, [delegate_id], {'active_id': task.id})
+
+ # Check delegation details
+ task.refresh()
+ self.assertEqual(task.planned_hours, 2, 'project_task_delegate: planned hours is not correct after delegation')
diff --git a/addons/project/wizard/__init__.py b/addons/project/wizard/__init__.py
index 7a9a734b2bd..24017d9cbe8 100644
--- a/addons/project/wizard/__init__.py
+++ b/addons/project/wizard/__init__.py
@@ -20,7 +20,5 @@
##############################################################################
import project_task_delegate
-import project_task_reevaluate
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/addons/project/wizard/project_task_reevaluate.py b/addons/project/wizard/project_task_reevaluate.py
deleted file mode 100644
index eeb8cccc5d2..00000000000
--- a/addons/project/wizard/project_task_reevaluate.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-from lxml import etree
-from openerp.osv import fields, osv
-from openerp.tools.translate import _
-
-class project_task_reevaluate(osv.osv_memory):
- _name = 'project.task.reevaluate'
-
- def _get_remaining(self, cr, uid, context=None):
- if context is None:
- context = {}
- active_id = context.get('active_id', False)
- res = False
- if active_id:
- res = self.pool.get('project.task').browse(cr, uid, active_id, context=context).remaining_hours
- return res
-
- _columns = {
- 'remaining_hours' : fields.float('Remaining Hours', digits=(16,2), help="Put here the remaining hours required to close the task."),
- }
-
- _defaults = {
- 'remaining_hours': _get_remaining,
- }
-
- def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
- res = super(project_task_reevaluate, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu=submenu)
- users_pool = self.pool.get('res.users')
- time_mode = users_pool.browse(cr, uid, uid, context).company_id.project_time_mode_id
- time_mode_name = time_mode and time_mode.name or 'Hours'
- if time_mode_name in ['Hours','Hour']:
- return res
-
- eview = etree.fromstring(res['arch'])
-
- def _check_rec(eview):
- if eview.attrib.get('widget','') == 'float_time':
- eview.set('widget','float')
- for child in eview:
- _check_rec(child)
- return True
-
- _check_rec(eview)
-
- res['arch'] = etree.tostring(eview)
-
- for field in res['fields']:
- if 'Hours' in res['fields'][field]['string']:
- res['fields'][field]['string'] = res['fields'][field]['string'].replace('Hours',time_mode_name)
- return res
-
- def compute_hours(self, cr, uid, ids, context=None):
- if context is None:
- context = {}
- data = self.browse(cr, uid, ids, context=context)[0]
- task_id = context.get('active_id')
- if task_id:
- task_pool = self.pool.get('project.task')
- task_pool.write(cr, uid, task_id, {'remaining_hours': data.remaining_hours})
- if context.get('button_reactivate'):
- task_pool.do_reopen(cr, uid, [task_id], context=context)
- return {'type': 'ir.actions.act_window_close'}
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project/wizard/project_task_reevaluate_view.xml b/addons/project/wizard/project_task_reevaluate_view.xml
deleted file mode 100644
index 6dfe58e8cfd..00000000000
--- a/addons/project/wizard/project_task_reevaluate_view.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
- Re-evaluate Task
- project.task.reevaluate
-
-
-
-
-
-
- Re-evaluate Task
- ir.actions.act_window
- project.task.reevaluate
- form
- form
- new
-
-
-
diff --git a/addons/project_gtd/project_gtd_view.xml b/addons/project_gtd/project_gtd_view.xml
index 4bfd5b783c7..8335ae35c93 100644
--- a/addons/project_gtd/project_gtd_view.xml
+++ b/addons/project_gtd/project_gtd_view.xml
@@ -79,7 +79,6 @@
-
@@ -103,16 +102,17 @@
-
-
+
-
-
-
+
+
+
+
+
diff --git a/addons/project_gtd/wizard/project_gtd_empty.py b/addons/project_gtd/wizard/project_gtd_empty.py
index 3233e2534af..b477c12567a 100644
--- a/addons/project_gtd/wizard/project_gtd_empty.py
+++ b/addons/project_gtd/wizard/project_gtd_empty.py
@@ -52,7 +52,7 @@ class project_timebox_empty(osv.osv_memory):
raise osv.except_osv(_('Error!'), _('No timebox child of this one!'))
tids = obj_task.search(cr, uid, [('timebox_id', '=', context['active_id'])])
for task in obj_task.browse(cr, uid, tids, context):
- if (task.state in ('cancel','done')) or (task.user_id.id <> uid):
+ if (task.stage_id and task.stage_id.fold) or (task.user_id.id <> uid):
close.append(task.id)
else:
up.append(task.id)
diff --git a/addons/project_issue/__openerp__.py b/addons/project_issue/__openerp__.py
index 2c5f311c044..d41144253dd 100644
--- a/addons/project_issue/__openerp__.py
+++ b/addons/project_issue/__openerp__.py
@@ -55,7 +55,6 @@ It allows the manager to quickly check the issues, assign them and decide on the
'test': [
'test/subscribe_issue.yml',
'test/issue_process.yml',
- 'test/cancel_issue.yml',
'test/issue_demo.yml'
],
'installable': True,
diff --git a/addons/project_issue/board_project_issue_view.xml b/addons/project_issue/board_project_issue_view.xml
index d7574042c37..6eb48f5572d 100644
--- a/addons/project_issue/board_project_issue_view.xml
+++ b/addons/project_issue/board_project_issue_view.xml
@@ -6,7 +6,7 @@
Project Issue Board Tree
project.issue
-
+
@@ -16,7 +16,6 @@
-
@@ -28,7 +27,7 @@
project.issue
form
tree,form
- [('state','not in',('cancel','done')),('user_id','=',uid)]
+ [('user_id', '=', uid)]
diff --git a/addons/project_issue/doc/changelog.rst b/addons/project_issue/doc/changelog.rst
new file mode 100644
index 00000000000..e74516f1600
--- /dev/null
+++ b/addons/project_issue/doc/changelog.rst
@@ -0,0 +1,13 @@
+.. _changelog:
+
+Changelog
+=========
+
+`trunk (saas-2)`
+----------------
+
+- Stage/state update
+
+ - ``project.issue``: removed inheritance from ``base_stage`` class and removed
+ ``state`` field. Added ``date_last_stage_update`` field holding last stage_id
+ modification. Updated reports.
diff --git a/addons/project_issue/doc/index.rst b/addons/project_issue/doc/index.rst
new file mode 100644
index 00000000000..9e12eef2124
--- /dev/null
+++ b/addons/project_issue/doc/index.rst
@@ -0,0 +1,22 @@
+=====================
+Project Issue DevDoc
+=====================
+
+Project Issue module documentation
+===================================
+
+Documentation topics
+''''''''''''''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ stage_status.rst
+
+Changelog
+'''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ changelog.rst
diff --git a/addons/project_issue/doc/stage_status.rst b/addons/project_issue/doc/stage_status.rst
new file mode 100644
index 00000000000..6afd4508cd9
--- /dev/null
+++ b/addons/project_issue/doc/stage_status.rst
@@ -0,0 +1,54 @@
+.. _stage_status:
+
+Stage and Status
+================
+
+.. versionchanged:: 8.0 saas-2 state/stage cleaning
+
+Stage
++++++
+
+This revision removed the concept of state on project.issue objects. The ``state``
+field has been totally removed and replaced by stages, using ``stage_id``. The
+following models are impacted:
+
+ - ``project.issue`` now use only stages. However a convention still exists about
+ 'New' stage. An issue is consdered as ``new`` when it has the following
+ properties:
+
+ - ``stage_id and stage_id.sequence = 1``
+
+ - ``project.task.type`` do not have any ``state`` field anymore.
+ - ``project.issue.report`` do not have any ``state`` field anymore.
+
+By default a newly created issue is in a new stage. It means that it will
+fetch the stage having ``sequence = 1``. Stage mangement is done using the
+kanban view or the clikable statusbar. It is not done using buttons anymore.
+
+Stage analysis
+++++++++++++++
+
+Stage analysis can be performed using the newly introduced ``date_last_stage_update``
+datetime field. This field is updated everytime ``stage_id`` is updated.
+
+``project.issue.report`` model also uses the ``date_last_stage_update`` field.
+This allows to group and analyse the time spend in the various stages.
+
+Open / Assignation date
++++++++++++++++++++++++
+
+The ``date_open`` field meaning has been updated. It is now set when the ``user_id``
+(responsible) is set. It is therefore the assignation date.
+
+Subtypes
+++++++++
+
+The following subtypes are triggered on ``project.issue``:
+
+ - ``mt_issue_new``: new tasks. Condition: ``obj.stage_id and obj.stage_id.sequence == 1``
+ - ``mt_issue_stage``: stage changed. Condition: ``obj.stage_id and obj.stage_id.sequence != 1``
+ - ``mt_issue_assigned``: user assigned. condition: ``obj.user_id and obj.user_id.id``
+ - ``mt_issue_blocked``: kanban state blocked. Condition: ``obj.kanban_state == 'blocked'``
+
+Those subtypes are also available on the ``project.project`` model and are used
+for the auto subscription.
diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py
index 91b5633f6d6..30ca08c0ae7 100644
--- a/addons/project_issue/project_issue.py
+++ b/addons/project_issue/project_issue.py
@@ -19,19 +19,16 @@
#
##############################################################################
-from openerp import SUPERUSER_ID
-from openerp.addons.base_status.base_stage import base_stage
-from openerp.addons.project.project import _TASK_STATE
-from openerp.addons.crm import crm
from datetime import datetime
-from openerp.osv import fields, osv, orm
-from openerp.tools.translate import _
-import binascii
-import time
-from openerp import tools
-from openerp.tools import html2plaintext
-class project_issue_version(osv.osv):
+from openerp import SUPERUSER_ID
+from openerp import tools
+from openerp.addons.crm import crm
+from openerp.osv import fields, osv, orm
+from openerp.tools import html2plaintext
+from openerp.tools.translate import _
+
+class project_issue_version(osv.Model):
_name = "project.issue.version"
_order = "name desc"
_columns = {
@@ -42,36 +39,25 @@ class project_issue_version(osv.osv):
'active': 1,
}
-class project_issue(base_stage, osv.osv):
+class project_issue(osv.Model):
_name = "project.issue"
_description = "Project Issue"
_order = "priority, create_date desc"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
- 'state': {
- 'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj.state in ['new', 'draft'],
- 'project_issue.mt_issue_closed': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
- 'project_issue.mt_issue_started': lambda self, cr, uid, obj, ctx=None: obj.state == 'open',
- },
'stage_id': {
- 'project_issue.mt_issue_stage': lambda self, cr, uid, obj, ctx=None: obj.state not in ['new', 'draft', 'done', 'open'],
+ 'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1,
+ 'project_issue.mt_issue_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence != 1,
+ },
+ 'user_id': {
+ 'project_issue.mt_issue_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id,
},
'kanban_state': {
'project_issue.mt_issue_blocked': lambda self, cr, uid, obj, ctx=None: obj.kanban_state == 'blocked',
},
}
- def create(self, cr, uid, vals, context=None):
- if context is None:
- context = {}
- if vals.get('project_id') and not context.get('default_project_id'):
- context['default_project_id'] = vals.get('project_id')
-
- # context: no_log, because subtype already handle this
- create_context = dict(context, mail_create_nolog=True)
- return super(project_issue, self).create(cr, uid, vals, context=create_context)
-
def _get_default_partner(self, cr, uid, context=None):
""" Override of base_stage to add project specific behavior """
project_id = self._get_default_project_id(cr, uid, context)
@@ -79,7 +65,7 @@ class project_issue(base_stage, osv.osv):
project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
if project and project.partner_id:
return project.partner_id.id
- return super(project_issue, self)._get_default_partner(cr, uid, context=context)
+ return False
def _get_default_project_id(self, cr, uid, context=None):
""" Gives default project by checking if present in the context """
@@ -88,7 +74,7 @@ class project_issue(base_stage, osv.osv):
def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """
project_id = self._get_default_project_id(cr, uid, context=context)
- return self.stage_find(cr, uid, [], project_id, [('state', '=', 'draft')], context=context)
+ return self.stage_find(cr, uid, [], project_id, [('sequence', '=', 1)], context=context)
def _resolve_project_id_from_context(self, cr, uid, context=None):
""" Returns ID of project based on the value of 'default_project_id'
@@ -258,13 +244,6 @@ class project_issue(base_stage, osv.osv):
'partner_id': fields.many2one('res.partner', 'Contact', select=1),
'company_id': fields.many2one('res.company', 'Company'),
'description': fields.text('Private Note'),
- 'state': fields.related('stage_id', 'state', type="selection", store=True,
- selection=_TASK_STATE, string="Status", readonly=True,
- help='The status is set to \'Draft\', when a case is created.\
- If the case is in progress the status is set to \'Open\'.\
- When the case is over, the status is set to \'Done\'.\
- If the case needs to be reviewed then the status is \
- set to \'Pending\'.'),
'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
track_visibility='onchange',
help="A Issue's kanban state indicates special situations affecting it:\n"
@@ -278,6 +257,7 @@ class project_issue(base_stage, osv.osv):
# Project Issue fields
'date_closed': fields.datetime('Closed', readonly=True,select=True),
'date': fields.datetime('Date'),
+ 'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel."),
'categ_ids': fields.many2many('project.category', string='Tags'),
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
@@ -313,13 +293,11 @@ class project_issue(base_stage, osv.osv):
_defaults = {
'active': 1,
- 'partner_id': lambda s, cr, uid, c: s._get_default_partner(cr, uid, c),
- 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c),
'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
- 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c),
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
'priority': crm.AVAILABLE_PRIORITIES[2][0],
'kanban_state': 'normal',
+ 'date_last_stage_update': fields.datetime.now(),
'user_id': lambda obj, cr, uid, context: uid,
}
@@ -373,7 +351,7 @@ class project_issue(base_stage, osv.osv):
})
vals = {
'task_id': new_task_id,
- 'stage_id': self.stage_find(cr, uid, [bug], bug.project_id.id, [('state', '=', 'pending')], context=context),
+ 'stage_id': self.stage_find(cr, uid, [bug], bug.project_id.id, [('sequence', '=', 1)], context=context),
}
message = _("Project issue converted to task.")
self.message_post(cr, uid, [bug.id], body=message, context=context)
@@ -401,19 +379,23 @@ class project_issue(base_stage, osv.osv):
return super(project_issue, self).copy(cr, uid, id, default=default,
context=context)
+ def create(self, cr, uid, vals, context=None):
+ if context is None:
+ context = {}
+ if vals.get('project_id') and not context.get('default_project_id'):
+ context['default_project_id'] = vals.get('project_id')
+
+ # context: no_log, because subtype already handle this
+ create_context = dict(context, mail_create_nolog=True)
+ return super(project_issue, self).create(cr, uid, vals, context=create_context)
+
def write(self, cr, uid, ids, vals, context=None):
-
- #Update last action date every time the user changes the stage
+ # stage change: update date_last_stage_update
if 'stage_id' in vals:
- vals['date_action_last'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
- state = self.pool.get('project.task.type').browse(cr, uid, vals['stage_id'], context=context).state
- for issue in self.browse(cr, uid, ids, context=context):
- # Change from draft to not draft EXCEPT cancelled: The issue has been opened -> set the opening date
- if issue.state == 'draft' and state not in ('draft', 'cancelled'):
- vals['date_open'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
- # Change from not done to done: The issue has been closed -> set the closing date
- if issue.state != 'done' and state == 'done':
- vals['date_closed'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
+ vals['date_last_stage_update'] = fields.datetime.now()
+ # user_id change: update date_start
+ if vals.get('user_id'):
+ vals['date_start'] = fields.datetime.now()
return super(project_issue, self).write(cr, uid, ids, vals, context)
@@ -423,13 +405,6 @@ class project_issue(base_stage, osv.osv):
task = self.pool.get('project.task').browse(cr, uid, task_id, context=context)
return {'value': {'user_id': task.user_id.id, }}
- def case_reset(self, cr, uid, ids, context=None):
- """Resets case as draft
- """
- res = super(project_issue, self).case_reset(cr, uid, ids, context)
- self.write(cr, uid, ids, {'date_open': False, 'date_closed': False})
- return res
-
def get_empty_list_help(self, cr, uid, help, context=None):
context['empty_list_help_model'] = 'project.project'
context['empty_list_help_id'] = context.get('default_project_id')
@@ -478,11 +453,6 @@ class project_issue(base_stage, osv.osv):
return stage_ids[0]
return False
- def case_cancel(self, cr, uid, ids, context=None):
- """ Cancels case """
- self.case_set(cr, uid, ids, 'cancelled', {'active': True}, context=context)
- return True
-
def case_escalate(self, cr, uid, ids, context=None):
cases = self.browse(cr, uid, ids)
for case in cases:
@@ -591,7 +561,7 @@ class project_issue(base_stage, osv.osv):
context = {}
res = super(project_issue, self).message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, content_subtype=content_subtype, **kwargs)
if thread_id and subtype:
- self.write(cr, SUPERUSER_ID, thread_id, {'date_action_last': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
+ self.write(cr, SUPERUSER_ID, thread_id, {'date_action_last': fields.datetime.now()}, context=context)
return res
@@ -605,7 +575,7 @@ class project(osv.Model):
res = dict.fromkeys(ids, 0)
issue_ids = self.pool.get('project.issue').search(cr, uid, [('project_id', 'in', ids)])
for issue in self.pool.get('project.issue').browse(cr, uid, issue_ids, context):
- if issue.state not in ('done', 'cancelled'):
+ if issue.stage_id and not issue.stage_id.fold:
res[issue.project_id.id] += 1
return res
diff --git a/addons/project_issue/project_issue_data.xml b/addons/project_issue/project_issue_data.xml
index 2e43ca69193..25a336d4094 100644
--- a/addons/project_issue/project_issue_data.xml
+++ b/addons/project_issue/project_issue_data.xml
@@ -35,11 +35,11 @@ Access all issues from the top Project menu, and access the issues of a specific
Issue created
-
- Issue Started
+
+ Issue Assigned
project.issue
- Issue started
+ Issue assigned
Issue Blocked
@@ -47,12 +47,6 @@ Access all issues from the top Project menu, and access the issues of a specific
Issue blocked
-
- Issue Closed
- project.issue
-
- Issue closed
-
Stage Changed
project.issue
@@ -67,11 +61,11 @@ Access all issues from the top Project menu, and access the issues of a specific
project_id
-
- Issue Started
+
+ Issue Assigned
project.project
-
+
project_id
@@ -80,12 +74,6 @@ Access all issues from the top Project menu, and access the issues of a specific
project_id
-
- Issue Closed
- project.project
-
- project_id
-
Issue Stage Changed
project.project
diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml
index da3034c72f5..1fbea33f3df 100644
--- a/addons/project_issue/project_issue_view.xml
+++ b/addons/project_issue/project_issue_view.xml
@@ -48,12 +48,6 @@
diff --git a/addons/project_issue/report/project_issue_report.py b/addons/project_issue/report/project_issue_report.py
index 5cca0ff8689..5f59b51f290 100644
--- a/addons/project_issue/report/project_issue_report.py
+++ b/addons/project_issue/report/project_issue_report.py
@@ -20,17 +20,10 @@
#
##############################################################################
-from openerp.osv import fields,osv
+from openerp.osv import fields, osv
from openerp import tools
from openerp.addons.crm import crm
-AVAILABLE_STATES = [
- ('draft','Draft'),
- ('open','Open'),
- ('cancel', 'Cancelled'),
- ('done', 'Closed'),
- ('pending','Pending')
-]
class project_issue_report(osv.osv):
_name = "project.issue.report"
_auto = False
@@ -38,18 +31,13 @@ class project_issue_report(osv.osv):
_columns = {
'name': fields.char('Year', size=64, required=False, readonly=True),
'section_id':fields.many2one('crm.case.section', 'Sale Team', readonly=True),
- 'state': fields.selection(AVAILABLE_STATES, 'Status', size=16, readonly=True),
- 'month':fields.selection([('01', 'January'), ('02', 'February'), \
- ('03', 'March'), ('04', 'April'),\
- ('05', 'May'), ('06', 'June'), \
- ('07', 'July'), ('08', 'August'),\
- ('09', 'September'), ('10', 'October'),\
- ('11', 'November'), ('12', 'December')], 'Month', readonly=True),
+ 'month':fields.selection(fields.date.MONTHS, 'Month', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'day': fields.char('Day', size=128, readonly=True),
'opening_date': fields.date('Date of Opening', readonly=True),
'creation_date': fields.date('Creation Date', readonly=True),
'date_closed': fields.date('Date of Closing', readonly=True),
+ 'date_last_stage_update': fields.date('Last Stage Update', readonly=True),
'stage_id': fields.many2one('project.task.type', 'Stage'),
'nbr': fields.integer('# of Issues', readonly=True),
'working_hours_open': fields.float('Avg. Working Hours to Open', readonly=True, group_operator="avg"),
@@ -80,7 +68,7 @@ class project_issue_report(osv.osv):
to_char(c.create_date, 'YYYY-MM-DD') as day,
to_char(c.date_open, 'YYYY-MM-DD') as opening_date,
to_char(c.create_date, 'YYYY-MM-DD') as creation_date,
- c.state,
+ date_trunc('day',c.date_last_stage_update) as date_last_stage_update,
c.user_id,
c.working_hours_open,
c.working_hours_close,
diff --git a/addons/project_issue/report/project_issue_report_view.xml b/addons/project_issue/report/project_issue_report_view.xml
index a885dcd8a7f..f9ca7168841 100644
--- a/addons/project_issue/report/project_issue_report_view.xml
+++ b/addons/project_issue/report/project_issue_report_view.xml
@@ -20,7 +20,7 @@
-
+
@@ -36,9 +36,9 @@
project.issue.report
-
-
+
+
@@ -49,14 +49,15 @@
-
-
-
-
+
+
+
+
@@ -70,6 +71,7 @@
+
diff --git a/addons/project_issue/test/cancel_issue.yml b/addons/project_issue/test/cancel_issue.yml
deleted file mode 100644
index 76ffa212638..00000000000
--- a/addons/project_issue/test/cancel_issue.yml
+++ /dev/null
@@ -1,60 +0,0 @@
--
- In order to test process of issue tracking in OpenERP, I cancel the unqualified Issue.
--
- !python {model: project.issue}: |
- self.case_cancel(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check the issue is in cancel state.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in cancel state}:
- - state == 'cancelled'
--
- I re-open the Issue.
--
- !python {model: project.issue}: |
- self.case_open(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check the state of issue after open it.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in open state}:
- - state == 'open'
--
- I put the issue in pending state.
--
- !python {model: project.issue}: |
- self.case_pending(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check the state of issue after put it in pending state.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in pending state}:
- - state == 'pending'
--
- I cancel the issue is in pending state.
--
- !python {model: project.issue}: |
- self.case_cancel(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check the issue is in cancel state.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in cancel state}:
- - state == 'cancelled'
--
- I close Issue.
--
- !python {model: project.issue}: |
- self.case_close(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check state of Issue after close.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in done state}:
- - state == 'done'
--
- I cancel the issue is in done state.
--
- !python {model: project.issue}: |
- self.case_cancel(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check the issue is in cancel state.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in cancel state}:
- - state == 'cancelled'
diff --git a/addons/project_issue/test/issue_process.yml b/addons/project_issue/test/issue_process.yml
index afb68a9b6c2..280df50ebea 100644
--- a/addons/project_issue/test/issue_process.yml
+++ b/addons/project_issue/test/issue_process.yml
@@ -1,23 +1,3 @@
--
- In order to test process of issue tracking in OpenERP, I Open the Issue.
--
- !python {model: project.issue}: |
- self.case_open(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check state of Issue after opened it.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in open state}:
- - state == 'open'
--
- Now I put Issue in pending due to need more information.
--
- !python {model: project.issue}: |
- self.case_pending(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check state after put in pending.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in pending state}:
- - state == 'pending'
-
I send mail to get more details. TODO revert mail.mail to mail.compose.message (conversion to customer should be automatic).
-
@@ -29,29 +9,9 @@
new_id = self.create(cr, uid, {'email_from': 'support@mycompany.com','email_to': 'Robert_Adersen@yahoo.com', 'subject': 'Regarding error in account module we nees more details'})
self.send_mail(cr, uid, [new_id], context=ctx)
except Exception, e:
- pass
--
- After getting sufficient details, I re-open Issue from pending state.
--
- !python {model: project.issue}: |
- self.case_open(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check state of Issue after re-opened.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in open state}:
- - state == 'open'
+ pass
-
I create Task for Issue.
-
!python {model: project.issue}: |
self.convert_issue_task(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I close Issue after resolving it
--
- !python {model: project.issue}: |
- self.case_close(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I Check state of Issue after closed.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in done state}:
- - state == 'done'
diff --git a/addons/project_long_term/project_long_term_view.xml b/addons/project_long_term/project_long_term_view.xml
index e61af0c6c32..e2f47489b4a 100644
--- a/addons/project_long_term/project_long_term_view.xml
+++ b/addons/project_long_term/project_long_term_view.xml
@@ -200,7 +200,7 @@
-
+
diff --git a/addons/project_long_term/security/ir.model.access.csv b/addons/project_long_term/security/ir.model.access.csv
index 2a00088b8f4..82bfab460cb 100644
--- a/addons/project_long_term/security/ir.model.access.csv
+++ b/addons/project_long_term/security/ir.model.access.csv
@@ -4,9 +4,6 @@ access_project_user_allocation,project.user.allocation,model_project_user_alloca
access_project_phase_manager,project.phase manager,model_project_phase,project.group_project_manager,1,1,1,1
access_project_user_allocation_manager,project.user.allocation manager,model_project_user_allocation,project.group_project_manager,1,1,1,1
access_resource_resource_user,user.user user,resource.model_resource_resource,project.group_project_user,1,0,0,0
-access_resource_calendar_leaves_user,user.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1
access_resource_resource_manager,user.user manager,resource.model_resource_resource,project.group_project_manager,1,1,1,1
access_project_user_allocation_manager,project.user.allocation.manager,model_project_user_allocation,project.group_project_manager,1,1,1,1
access_project_resource_calendar_attendance,resource.calendar.attendance,resource.model_resource_calendar_attendance,project.group_project_manager,1,0,0,0
-
-
diff --git a/addons/project_long_term/test/task_process.yml b/addons/project_long_term/test/task_process.yml
index a7065de315f..060abcf43f3 100644
--- a/addons/project_long_term/test/task_process.yml
+++ b/addons/project_long_term/test/task_process.yml
@@ -14,6 +14,6 @@
!python {model: project.project}: |
prj = self.browse(cr, uid, [ref("project.project_project_1")])[0]
for task in prj.tasks:
- if task.state in ('done','cancelled'):
+ if task.stage_id and task.stage_id.fold:
continue
assert task.user_id and task.date_start and task.date_end, "Project tasks not scheduled"
diff --git a/addons/project_mrp/project_mrp.py b/addons/project_mrp/project_mrp.py
index bb25b4aae24..d5ed55a8d31 100644
--- a/addons/project_mrp/project_mrp.py
+++ b/addons/project_mrp/project_mrp.py
@@ -22,6 +22,23 @@
from openerp.osv import fields, osv
from openerp import netsvc
+
+class ProjectTaskStageMrp(osv.Model):
+ """ Override project.task.type model to add a 'closed' boolean field allowing
+ to know that tasks in this stage are considered as closed. Indeed since
+ OpenERP 8.0 status is not present on tasks anymore, only stage_id. """
+ _name = 'project.task.type'
+ _inherit = 'project.task.type'
+
+ _columns = {
+ 'closed': fields.boolean('Close', help="Tasks in this stage are considered as closed."),
+ }
+
+ _defaults = {
+ 'closed': False,
+ }
+
+
class project_task(osv.osv):
_name = "project.task"
_inherit = "project.task"
@@ -30,21 +47,21 @@ class project_task(osv.osv):
'sale_line_id': fields.related('procurement_id', 'sale_line_id', type='many2one', relation='sale.order.line', store=True, string='Sales Order Line'),
}
- def _validate_subflows(self, cr, uid, ids):
+ def _validate_subflows(self, cr, uid, ids, context=None):
wf_service = netsvc.LocalService("workflow")
for task in self.browse(cr, uid, ids):
if task.procurement_id:
wf_service.trg_write(uid, 'procurement.order', task.procurement_id.id, cr)
- def do_close(self, cr, uid, ids, *args, **kwargs):
- res = super(project_task, self).do_close(cr, uid, ids, *args, **kwargs)
- self._validate_subflows(cr, uid, ids)
+ def write(self, cr, uid, ids, values, context=None):
+ """ When closing tasks, validate subflows. """
+ res = super(project_task, self).write(cr, uid, ids, values, context=context)
+ if values.get('stage_id'):
+ stage = self.pool.get('project.task.type').browse(cr, uid, values.get('stage_id'), context=context)
+ if stage.closed:
+ self._validate_subflows(cr, uid, ids, context=context)
return res
- def do_cancel(self, cr, uid, ids, *args, **kwargs):
- res = super(project_task, self).do_cancel(cr, uid, ids, *args, **kwargs)
- self._validate_subflows(cr, uid, ids)
- return res
class product_product(osv.osv):
_inherit = "product.product"
@@ -52,8 +69,9 @@ class product_product(osv.osv):
'project_id': fields.many2one('project.project', 'Project', ondelete='set null',)
}
+
class sale_order(osv.osv):
- _inherit ='sale.order'
+ _inherit = 'sale.order'
def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, context=None):
proc_data = super(sale_order, self)._prepare_order_line_procurement(cr,
@@ -66,11 +84,12 @@ class sale_order(osv.osv):
return {}
res_sale = {}
res = super(sale_order, self)._picked_rate(cr, uid, ids, name, arg, context=context)
- cr.execute('''select sol.order_id as sale_id, t.state as task_state ,
+ cr.execute('''select sol.order_id as sale_id, stage.closed as task_closed ,
t.id as task_id, sum(sol.product_uom_qty) as total
from project_task as t
left join sale_order_line as sol on sol.id = t.sale_line_id
- where sol.order_id in %s group by sol.order_id,t.state,t.id ''',(tuple(ids),))
+ left join project_task_type as stage on stage.id = t.stage_id
+ where sol.order_id in %s group by sol.order_id,stage.closed,t.id ''',(tuple(ids),))
sale_task_data = cr.dictfetchall()
if not sale_task_data:
@@ -90,7 +109,7 @@ class sale_order(osv.osv):
for item in sale_task_data:
res_sale[item['sale_id']]['total_no_task'] += item['total']
- if item['task_state'] == 'done':
+ if item['task_closed']:
res_sale[item['sale_id']]['number_of_done'] += item['total']
for sale in self.browse(cr, uid, ids, context=context):
diff --git a/addons/project_mrp/project_mrp_demo.xml b/addons/project_mrp/project_mrp_demo.xml
index 30d4cfba7ef..a840c0166e7 100644
--- a/addons/project_mrp/project_mrp_demo.xml
+++ b/addons/project_mrp/project_mrp_demo.xml
@@ -1,7 +1,6 @@
-
@@ -14,5 +13,8 @@
make_to_order
+
+
+
diff --git a/addons/project_mrp/project_mrp_view.xml b/addons/project_mrp/project_mrp_view.xml
index 499a0259214..f724a3c76b6 100644
--- a/addons/project_mrp/project_mrp_view.xml
+++ b/addons/project_mrp/project_mrp_view.xml
@@ -21,14 +21,24 @@
+
+ project.task.type.mrp.inherit
+ project.task.type
+
+
+
+
+
+
+
project.mrp.form.view.inherit
project.task
-
+
-
+
diff --git a/addons/project_mrp/project_procurement.py b/addons/project_mrp/project_procurement.py
index f4b4a28db46..8a6dc811d9a 100644
--- a/addons/project_mrp/project_procurement.py
+++ b/addons/project_mrp/project_procurement.py
@@ -33,13 +33,13 @@ class procurement_order(osv.osv):
def action_check_finished(self, cr, uid, ids):
res = super(procurement_order, self).action_check_finished(cr, uid, ids)
return res and self.check_task_done(cr, uid, ids)
-
+
def check_task_done(self, cr, uid, ids, context=None):
""" Checks if task is done or not.
@return: True or False.
"""
for p in self.browse(cr, uid, ids, context=context):
- if (p.product_id.type=='service') and (p.procure_method=='make_to_order') and p.task_id and (p.task_id.state not in ('done', 'cancelled')):
+ if (p.product_id.type == 'service') and (p.procure_method == 'make_to_order') and p.task_id and (p.task_id.stage_id and not p.task_id.stage_id.closed):
return False
return True
diff --git a/addons/project_mrp/test/project_task_procurement.yml b/addons/project_mrp/test/project_task_procurement.yml
index 967461ca30a..b16cce7590d 100644
--- a/addons/project_mrp/test/project_task_procurement.yml
+++ b/addons/project_mrp/test/project_task_procurement.yml
@@ -36,7 +36,7 @@
!python {model: project.task}: |
task_ids = self.search(cr, uid, [('sale_line_id', '=', ref('line_services'))])
assert task_ids, "Task is not generated for Service Order Line."
- self.do_close(cr, uid, task_ids, context=context)
+ self.write(cr, uid, task_ids, {'stage_id': ref('project.project_tt_deployment')}, context=context)
-
I check procurement of Service Order Line after closed task.
-
@@ -44,4 +44,4 @@
procurement_ids = self.search(cr, uid, [('sale_line_id', '=', ref('line_services'))])
assert procurement_ids, "Procurement is not generated for Service Order Line."
procurement = self.browse(cr, uid, procurement_ids[0], context=context)
- assert procurement.state == 'done' , "Procurement should be closed."
+ assert procurement.state == 'done' , "Procurement should be closed."
\ No newline at end of file
diff --git a/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml b/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml
index 3efd133ffb6..7d7566bdd0c 100644
--- a/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml
+++ b/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml
@@ -66,16 +66,7 @@
planned_hours: 20.0
project_id: project_project_timesheetmanagement0
remaining_hours: 20.0
- state: draft
user_id: res_users_hrmanager0
--
- Open the task
--
- !python {model: project.task}: |
- self.do_open(cr, uid, [ref("project_task_getalltimesheetrecords0")], {"lang":
- "en_US", "active_ids": [ref("project_project_timesheetmanagement0")], "tz":
- False, "active_model": "project.project", "department_id": False, "project_id":
- False, "active_id": ref("project_project_timesheetmanagement0"), })
-
Make a work task entry 'Get work calendar of all employees' of 10 hours done by HR manager
-