[MERGE] Sync with trunk

bzr revid: tde@openerp.com-20130719100119-ohpzxw4r1fxta93k
This commit is contained in:
Thibault Delavallée 2013-07-19 12:01:19 +02:00
commit 163d88ea32
57 changed files with 993 additions and 1089 deletions

View File

@ -604,7 +604,7 @@ class crm_lead(format_address, osv.osv):
attachment.write(values)
return True
def merge_opportunity(self, cr, uid, ids, context=None):
def merge_opportunity(self, cr, uid, ids, user_id=False, section_id=False, context=None):
"""
Different cases of merge:
- merge leads together = 1 new lead
@ -639,6 +639,11 @@ class crm_lead(format_address, osv.osv):
fields = list(CRM_LEAD_FIELDS_TO_MERGE)
merged_data = self._merge_data(cr, uid, ids, highest, fields, context=context)
if user_id:
merged_data['user_id'] = user_id
if section_id:
merged_data['section_id'] = section_id
# Merge messages and attachements into the first opportunity
self._merge_opportunity_history(cr, uid, highest.id, tail_opportunities, context=context)
self._merge_opportunity_attachments(cr, uid, highest.id, tail_opportunities, context=context)

View File

@ -34,6 +34,8 @@ class crm_lead2opportunity_partner(osv.osv_memory):
('merge', 'Merge with existing opportunities')
], 'Conversion Action', required=True),
'opportunity_ids': fields.many2many('crm.lead', string='Opportunities'),
'user_id': fields.many2one('res.users', 'Salesperson', select=True),
'section_id': fields.many2one('crm.case.section', 'Sales Team', select=True),
}
def default_get(self, cr, uid, fields, context=None):
@ -73,9 +75,27 @@ class crm_lead2opportunity_partner(osv.osv_memory):
res.update({'name' : len(tomerge) >= 2 and 'merge' or 'convert'})
if 'opportunity_ids' in fields and len(tomerge) >= 2:
res.update({'opportunity_ids': list(tomerge)})
if lead.user_id:
res.update({'user_id': lead.user_id.id})
if lead.section_id:
res.update({'section_id': lead.section_id.id})
return res
def on_change_user(self, cr, uid, ids, user_id, section_id, context=None):
""" When changing the user, also set a section_id or restrict section id
to the ones user_id is member of. """
if user_id:
if section_id:
user_in_section = self.pool.get('crm.case.section').search(cr, uid, [('id', '=', section_id), '|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context, count=True)
else:
user_in_section = False
if not user_in_section:
section_id = False
section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
if section_ids:
section_id = section_ids[0]
return {'value': {'section_id': section_id}}
def view_init(self, cr, uid, fields, context=None):
"""
Check some preconditions before the wizard executes.
@ -117,15 +137,15 @@ class crm_lead2opportunity_partner(osv.osv_memory):
w = self.browse(cr, uid, ids, context=context)[0]
opp_ids = [o.id for o in w.opportunity_ids]
if w.name == 'merge':
lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, opp_ids, context=context)
lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, opp_ids, w.user_id.id, w.section_id.id, context=context)
lead_ids = [lead_id]
lead = self.pool.get('crm.lead').read(cr, uid, lead_id, ['type'], context=context)
if lead['type'] == "lead":
context.update({'active_ids': lead_ids})
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids, 'user_ids': [w.user_id.id], 'section_id': w.section_id.id}, context=context)
else:
lead_ids = context.get('active_ids', [])
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids, 'user_ids': [w.user_id.id], 'section_id': w.section_id.id}, context=context)
return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)

View File

@ -10,6 +10,10 @@
<group name="name">
<field name="name" class="oe_inline"/>
</group>
<group string="Assign opportunities to">
<field name="user_id" class="oe_inline" on_change="on_change_user(user_id, section_id, context)"/>
<field name="section_id" class="oe_inline"/>
</group>
<group string="Opportunities">
<field name="opportunity_ids" attrs="{'invisible': [('name', '!=', 'merge')]}" nolabel="1">
<tree>
@ -56,6 +60,10 @@
attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"
class="oe_inline"/>
</group>
<group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}">
<field name="section_id" groups="base.group_multi_salesteams"/>
<field name="user_ids" widget="many2many_tags"/>
</group>
<group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
<field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">
<tree>
@ -72,12 +80,6 @@
</tree>
</field>
</group>
<group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}">
<field name="section_id" groups="base.group_multi_salesteams"/>
<field name="user_ids" widget="many2many_tags"/>
</group>
<footer>
<button name="mass_convert" string="Convert to Opportunities" type="object" class="oe_highlight"/>
or

View File

@ -34,6 +34,8 @@ class crm_merge_opportunity(osv.osv_memory):
_description = 'Merge opportunities'
_columns = {
'opportunity_ids': fields.many2many('crm.lead', rel='merge_opportunity_rel', id1='merge_id', id2='opportunity_id', string='Leads/Opportunities'),
'user_id': fields.many2one('res.users', 'Salesperson', select=True),
'section_id': fields.many2one('crm.case.section', 'Sales Team', select=True),
}
def action_merge(self, cr, uid, ids, context=None):
@ -47,7 +49,7 @@ class crm_merge_opportunity(osv.osv_memory):
#TODO: why is this passed through the context ?
context['lead_ids'] = [opportunity2merge_ids[0].id]
merge_id = lead_obj.merge_opportunity(cr, uid, [x.id for x in opportunity2merge_ids], context=context)
merge_id = lead_obj.merge_opportunity(cr, uid, [x.id for x in opportunity2merge_ids], wizard.user_id.id, wizard.section_id.id, context=context)
# The newly created lead might be a lead or an opp: redirect toward the right view
merge_result = lead_obj.browse(cr, uid, merge_id, context=context)
@ -78,4 +80,19 @@ class crm_merge_opportunity(osv.osv_memory):
return res
def on_change_user(self, cr, uid, ids, user_id, section_id, context=None):
""" When changing the user, also set a section_id or restrict section id
to the ones user_id is member of. """
if user_id:
if section_id:
user_in_section = self.pool.get('crm.case.section').search(cr, uid, [('id', '=', section_id), '|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context, count=True)
else:
user_in_section = False
if not user_in_section:
section_id = False
section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
if section_ids:
section_id = section_ids[0]
return {'value': {'section_id': section_id}}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -8,20 +8,25 @@
<field name="model">crm.merge.opportunity</field>
<field name="arch" type="xml">
<form string="Merge Leads/Opportunities" version="7.0">
<separator string="Select Leads/Opportunities"/>
<field name="opportunity_ids">
<tree>
<field name="create_date"/>
<field name="name"/>
<field name="type"/>
<field name="contact_name"/>
<field name="email_from"/>
<field name="phone"/>
<field name="stage_id"/>
<field name="user_id"/>
<field name="section_id" groups="base.group_multi_salesteams"/>
</tree>
</field>
<group string="Assign opportunities to">
<field name="user_id" class="oe_inline" on_change="on_change_user(user_id, context)"/>
<field name="section_id" class="oe_inline"/>
</group>
<group string="Select Leads/Opportunities">
<field name="opportunity_ids" nolabel="1">
<tree>
<field name="create_date"/>
<field name="name"/>
<field name="type"/>
<field name="contact_name"/>
<field name="email_from"/>
<field name="phone"/>
<field name="stage_id"/>
<field name="user_id"/>
<field name="section_id" groups="base.group_multi_salesteams"/>
</tree>
</field>
</group>
<footer>
<button name="action_merge" type="object" string="Merge" class="oe_highlight"/>
or

View File

@ -38,7 +38,7 @@ You can also use the geolocalization without using the GPS coordinates.
""",
'author': 'OpenERP SA',
'depends': ['crm', 'account', 'portal'],
'demo': ['res_partner_demo.xml'],
'demo': ['res_partner_demo.xml', 'crm_lead_demo.xml'],
'data': [
'security/ir.model.access.csv',
'res_partner_view.xml',

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data noupdate="1">
<!-- Demo Leads -->
<record id="crm_case_partner_assign_1" model="crm.lead">
<field name="type">lead</field>
<field name="name">Specifications and price of your phones</field>
<field name="contact_name">Steve Martinez</field>
<field name="partner_name"></field>
<field name="partner_id" ref=""/>
<field name="function">Reseller</field>
<field name="country_id" ref="base.uk"/>
<field name="city">Edinburgh</field>
<field name="type_id" ref="crm.type_lead8"/>
<field name="categ_ids" eval="[(6, 0, [ref('crm.categ_oppor1')])]"/>
<field name="channel_id" ref="crm.crm_case_channel_email"/>
<field name="priority">2</field>
<field name="section_id" ref="crm.crm_case_section_2"/>
<field name="user_id" ref=""/>
<field name="stage_id" ref="crm.stage_lead1"/>
<field name="description">Hi,
Please, can you give me more details about your phones, including their specifications and their prices.
Regards,
Steve</field>
<field eval="1" name="active"/>
<field name="partner_assigned_id" ref="portal.partner_demo_portal"/>
</record>
</data>
</openerp>

View File

@ -16,9 +16,8 @@
<field name="user_id"/>
<field string="Timebox" name="timebox_id"/>
<!--TODO : Need To Clean-->
<!-- <button name="prev_timebox" type="object" icon="gtk-go-back" string="Previous" attrs="{'invisible': [('probability', '=', 100)]}"/>-->
<!-- <button name="next_timebox" type="object" icon="gtk-go-forward" string="Next" attrs="{'invisible': [('probability', '=', 100)]}"/>-->
<button name="prev_timebox" type="object" string="Previous"/>
<button name="next_timebox" type="object" string="Next"/>
</tree>
</field>
</page>

View File

@ -6,7 +6,7 @@
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<field name="description" position="replace">
<field name="description_pad" attrs="{'readonly':[('state','=','done')]}" widget="pad"/>
<field name="description_pad" widget="pad"/>
</field>
</field>
</record>

View File

@ -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'])

View File

@ -26,7 +26,7 @@
</div>
<div><field name="categ_ids" widget="many2many_tags" class="oe_left"/></div>
<div class="oe_text_right">
<h1><field name="state" readonly="1"/></h1>
<h1><field name="stage_id" readonly="1"/></h1>
</div>
</div>
<div>

View File

@ -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')

View File

@ -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,

View File

@ -16,7 +16,6 @@
<field name="effective_hours" widget="float_time"/>
<field name="progress" widget="progressbar"/>
<field name="stage_id" invisible="context.get('set_visible',False)"/>
<field name="state" invisible="1"/>
</tree>
</field>
</record>
@ -26,7 +25,7 @@
<field name="res_model">project.task</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('user_id','=',uid),('state','not in',('cancel','done'))]</field>
<field name="domain">[('user_id', '=', uid), ('stage_id.fold', '!=', True)]</field>
<field name="view_id" ref="view_task_tree"/>
</record>

View File

@ -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.

View File

@ -0,0 +1,22 @@
=====================
Project DevDoc
=====================
Project module documentation
===================================
Documentation topics
''''''''''''''''''''
.. toctree::
:maxdepth: 1
stage_status.rst
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst

View File

@ -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.

View File

@ -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

View File

@ -30,51 +30,43 @@
<record id="project_tt_analysis" model="project.task.type">
<field name="sequence">1</field>
<field name="name">Analysis</field>
<field name="state">draft</field>
<field name="case_default" eval="False"/>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_specification" model="project.task.type">
<field name="sequence">2</field>
<field name="sequence">10</field>
<field name="name">Specification</field>
<field name="state">pending</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_design" model="project.task.type">
<field name="sequence">2</field>
<field name="sequence">11</field>
<field name="name">Design</field>
<field name="state">open</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_development" model="project.task.type">
<field name="sequence">3</field>
<field name="sequence">12</field>
<field name="name">Development</field>
<field name="state">open</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_testing" model="project.task.type">
<field name="sequence">4</field>
<field name="sequence">13</field>
<field name="name">Testing</field>
<field name="state">open</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_merge" model="project.task.type">
<field name="sequence">5</field>
<field name="sequence">14</field>
<field name="name">Merge</field>
<field name="state">open</field>
<field name="case_default" eval="False"/>
<field name="fold" eval="True"/>
</record>
<record id="project_tt_deployment" model="project.task.type">
<field name="sequence">100</field>
<field name="sequence">20</field>
<field name="name">Done</field>
<field name="state">done</field>
<field name="case_default" eval="True"/>
<field name="fold" eval="True"/>
</record>
<record id="project_tt_cancel" model="project.task.type">
<field name="sequence">200</field>
<field name="sequence">30</field>
<field name="name">Cancelled</field>
<field name="state">cancelled</field>
<field name="case_default" eval="True"/>
<field name="fold" eval="True"/>
</record>
@ -86,11 +78,11 @@
<field name="default" eval="False"/>
<field name="description">Task created</field>
</record>
<record id="mt_task_started" model="mail.message.subtype">
<field name="name">Task Started</field>
<record id="mt_task_assigned" model="mail.message.subtype">
<field name="name">Task Assigned</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task started</field>
<field name="description">Task Assigned</field>
</record>
<record id="mt_task_blocked" model="mail.message.subtype">
<field name="name">Task Blocked</field>
@ -98,12 +90,6 @@
<field name="default" eval="False"/>
<field name="description">Task blocked</field>
</record>
<record id="mt_task_closed" model="mail.message.subtype">
<field name="name">Task Done</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task closed</field>
</record>
<record id="mt_task_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field>
<field name="res_model">project.task</field>
@ -118,11 +104,11 @@
<field name="parent_id" eval="ref('mt_task_new')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_started" model="mail.message.subtype">
<field name="name">Task Started</field>
<record id="mt_project_task_assigned" model="mail.message.subtype">
<field name="name">Task Assigned</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_task_started')"/>
<field name="parent_id" eval="ref('mt_task_assigned')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_blocked" model="mail.message.subtype">
@ -131,12 +117,6 @@
<field name="parent_id" eval="ref('mt_task_blocked')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_closed" model="mail.message.subtype">
<field name="name">Task Done</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_task_closed')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_stage" model="mail.message.subtype">
<field name="name">Task Stage Changed</field>
<field name="res_model">project.project</field>

View File

@ -225,7 +225,6 @@
ref('project.project_category_04')])]"/>
<field name="stage_id" ref="project_tt_merge"/>
</record>
<function model="project.task" name="do_close" eval="[ref('project_task_11')], {'install_mode': True}"/>
<record id="project_task_12" model="project.task">
<field name="planned_hours" eval="40.0"/>
@ -237,7 +236,6 @@
<field name="stage_id" ref="project_tt_merge"/>
<field name="color">6</field>
</record>
<function model="project.task" name="do_close" eval="[ref('project_task_12')], {'install_mode': True}"/>
<record id="project_task_13" model="project.task">
<field name="planned_hours" eval="12.0"/>
@ -248,7 +246,6 @@
<field name="name">Design Use Cases</field>
<field name="stage_id" ref="project_tt_analysis"/>
</record>
<function model="project.task" name="do_pending" eval="[ref('project_task_13')], {'install_mode': True}"/>
<record id="project_task_14" model="project.task">
<field name="planned_hours" eval="12.0"/>
@ -282,7 +279,6 @@
<field name="name">Set target for all deparments</field>
<field name="stage_id" ref="project_tt_development"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_16')], {'install_mode': True}"/>
<record id="project_task_17" model="project.task">
<field name="planned_hours" eval="34.0"/>
@ -293,7 +289,6 @@
<field name="name">Integration of core components</field>
<field name="stage_id" ref="project_tt_testing"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_17')], {'install_mode': True}"/>
<record id="project_task_18" model="project.task">
<field name="planned_hours" eval="16.0"/>
@ -315,7 +310,6 @@
<field name="categ_ids" eval="[(6, 0, [
ref('project_category_03')])]"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_19')], {'install_mode': True}"/>
<record id="project_task_20" model="project.task">
<field name="planned_hours">42.0</field>
@ -325,7 +319,6 @@
<field name="project_id" ref="project.project_project_4"/>
<field name="name">Create new components</field>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_20')], {'install_mode': True}"/>
<record id="project_task_21" model="project.task">
<field name="planned_hours">14.0</field>
@ -337,7 +330,6 @@
<field name="categ_ids" eval="[(6, 0, [
ref('project_category_04')])]"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_21')], {'install_mode': True}"/>
<record id="project_task_22" model="project.task">
<field name="planned_hours">12.0</field>
@ -371,7 +363,6 @@
<field name="categ_ids" eval="[(6, 0, [
ref('project_category_01')])]"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_24')], {'install_mode': True}"/>
<record id="project_task_25" model="project.task">
<field name="sequence">20</field>
@ -382,7 +373,6 @@
<field name="name">Data importation + Doc</field>
<field name="stage_id" ref="project_tt_development"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_25')], {'install_mode': True}"/>
<record id="project_task_26" model="project.task">
<field name="sequence">20</field>

View File

@ -18,29 +18,26 @@
<search string="Tasks">
<field name="name" string="Tasks"/>
<field name="categ_ids"/>
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
<filter string="New" name="draft" domain="[('stage_id.sequence', '=', 1)]"/>
<separator/>
<filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<filter string="My Tasks" domain="[('user_id','=',uid)]"/>
<separator/>
<filter name="draft" string="New" domain="[('state','=','draft')]" help="New Tasks" icon="terp-check"/>
<filter name="open" string="In Progress" domain="[('state','=','open')]" help="In Progress Tasks" icon="terp-camera_test"/>
<filter string="Pending" domain="[('state','=','pending')]" context="{'show_delegated':False}" help="Pending Tasks" icon="terp-gtk-media-pause"/>
<separator/>
<filter name="My project" string="Project" domain="[('project_id.user_id','=',uid)]" help="My Projects" icon="terp-check"/>
<separator/>
<filter string="My Tasks" domain="[('user_id','=',uid)]" help="My Tasks" icon="terp-personal"/>
<filter string="Unassigned Tasks" domain="[('user_id','=',False)]" help="Unassigned Tasks" icon="terp-personal-"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter string="Deadlines" context="{'deadline_visible': False}" domain="[('date_deadline','&lt;&gt;',False)]"
help="Show only tasks having a deadline" icon="terp-gnome-cpu-frequency-applet+"/>
help="Show only tasks having a deadline"/>
<field name="partner_id"/>
<field name="project_id"/>
<field name="user_id"/>
<field name="stage_id" domain="[]"/>
<group expand="0" string="Group By...">
<filter string="Users" name="group_user_id" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Project" name="group_project_id" icon="terp-folder-violet" domain="[]" context="{'group_by':'project_id'}"/>
<filter string="Stage" name="group_stage_id" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Last Stage Update" icon="terp-go-month" domain="[]" context="{'group_by':'date_last_stage_update'}"/>
<filter string="Deadline" icon="terp-gnome-cpu-frequency-applet+" domain="[]" context="{'group_by':'date_deadline'}"/>
<filter string="Start Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_start'}" groups="base.group_no_one"/>
<filter string="Start Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_start'}"/>
<filter string="End Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_end'}" groups="base.group_no_one"/>
</group>
</search>
@ -371,18 +368,6 @@
<field name="arch" type="xml">
<form string="Project" version="7.0">
<header>
<!--
<button name="do_open" string="Start Task" type="object"
states="draft,pending" class="oe_highlight"/>
<button name="do_draft" string="Draft" type="object"
states="cancel,done"/>
-->
<button name="project_task_reevaluate" string="Reactivate" type="object"
states="cancelled,done" context="{'button_reactivate':True}" groups="base.group_user"/>
<button name="action_close" string="Done" type="object"
states="draft,open,pending" groups="base.group_user"/>
<button name="do_cancel" string="Cancel Task" type="object"
states="draft,open,pending" groups="base.group_user"/>
<field name="stage_id" widget="statusbar" clickable="True"/>
</header>
<sheet string="Task">
@ -396,8 +381,7 @@
<group>
<group>
<field name="project_id" on_change="onchange_project(project_id)" context="{'default_use_tasks':1}"/>
<field name="user_id"
attrs="{'readonly':[('state','in',['done', 'cancelled'])]}"
<field name="user_id"
options='{"no_open": True}'
context="{'default_groups_ref': ['base.group_user', 'project.group_project_user']}"/>
<field name="planned_hours" widget="float_time"
@ -405,15 +389,15 @@
on_change="onchange_planned(planned_hours, effective_hours)"/>
</group>
<group>
<field name="date_deadline" attrs="{'readonly':[('state','in',['done', 'cancelled'])]}"/>
<field name="date_deadline"/>
<field name="categ_ids" widget="many2many_tags"/>
<field name="progress" widget="progressbar"
groups="project.group_time_work_estimation_tasks" attrs="{'invisible':[('state','=','cancelled')]}"/>
groups="project.group_time_work_estimation_tasks"/>
</group>
</group>
<notebook>
<page string="Description">
<field name="description" attrs="{'readonly':[('state','=','done')]}" placeholder="Add a Description..."/>
<field name="description" placeholder="Add a Description..."/>
<field name="work_ids" groups="project.group_tasks_work_on_tasks">
<tree string="Task Work" editable="top">
<field name="name"/>
@ -427,7 +411,7 @@
<field name="effective_hours" widget="float_time"/>
<label for="remaining_hours" string="Remaining" groups="project.group_time_work_estimation_tasks"/>
<div>
<field name="remaining_hours" widget="float_time" attrs="{'readonly':[('state','in',('done','cancelled'))]}" groups="project.group_time_work_estimation_tasks"/>
<field name="remaining_hours" widget="float_time" groups="project.group_time_work_estimation_tasks"/>
</div>
<field name="total_hours" widget="float_time" class="oe_subtotal_footer_separator"/>
</group>
@ -436,7 +420,7 @@
</page>
<page string="Delegation" groups="project.group_delegate_task">
<button name="%(action_project_task_delegate)d" string="Delegate" type="action"
states="pending,open,draft" groups="project.group_delegate_task"/>
groups="project.group_delegate_task"/>
<separator string="Parent Tasks"/>
<field name="parent_ids"/>
<separator string="Delegated tasks"/>
@ -445,7 +429,6 @@
<field name="name"/>
<field name="user_id"/>
<field name="stage_id"/>
<field name="state" invisible="1"/>
<field name="effective_hours" widget="float_time"/>
<field name="progress" widget="progressbar"/>
<field name="remaining_hours" widget="float_time"/>
@ -453,12 +436,11 @@
</tree>
</field>
</page>
<page string="Extra Info" attrs="{'readonly':[('state','=','done')]}">
<page string="Extra Info">
<group col="4">
<field name="priority" groups="base.group_user"/>
<field name="sequence"/>
<field name="partner_id"/>
<field name="state" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
<group>
@ -467,6 +449,7 @@
<field name="date_end"/>
</group>
<group>
<field name="date_last_stage_update" groups="base.group_no_one"/>
</group>
</group>
</page>
@ -493,7 +476,6 @@
<field name="user_email"/>
<field name="description"/>
<field name="sequence"/>
<field name="state" groups="base.group_no_one"/>
<field name="kanban_state"/>
<field name="remaining_hours" sum="Remaining Time" groups="project.group_time_work_estimation_tasks"/>
<field name="date_deadline"/>
@ -513,7 +495,6 @@
<li><a name="set_remaining_time_2" type="object" class="oe_kanban_button">2</a></li>
<li><a name="set_remaining_time_5" type="object" class="oe_kanban_button">5</a></li>
<li><a name="set_remaining_time_10" type="object" class="oe_kanban_button">10</a></li>
<li><a name="do_open" states="draft" string="Validate planned time" type="object" class="oe_kanban_button oe_kanban_button_active">!</a></li>
</ul>
</li>
<br/>
@ -562,7 +543,7 @@
<field name="model">project.task</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<tree fonts="bold:message_unread==True" colors="grey:state in ('cancelled','done');blue:state == 'pending';red:date_deadline and (date_deadline&lt;current_date) and (state in ('draft','pending','open'))" string="Tasks">
<tree fonts="bold:message_unread==True" colors="red:date_deadline and (date_deadline&lt;current_date)" string="Tasks">
<field name="message_unread" invisible="1"/>
<field name="sequence" invisible="not context.get('seq_visible', False)"/>
<field name="name"/>
@ -575,7 +556,6 @@
<field name="remaining_hours" widget="float_time" sum="Remaining Hours" on_change="onchange_remaining(remaining_hours,planned_hours)" invisible="context.get('set_visible',False)" groups="project.group_time_work_estimation_tasks"/>
<field name="date_deadline" invisible="context.get('deadline_visible',True)"/>
<field name="stage_id" invisible="context.get('set_visible',False)"/>
<field name="state" invisible="1"/>
<field name="date_start" groups="base.group_no_one"/>
<field name="date_end" groups="base.group_no_one"/>
<field name="progress" widget="progressbar" invisible="context.get('set_visible',False)"/>
@ -661,7 +641,7 @@
<field name="res_model">project.task</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,graph,kanban</field>
<field name="domain">[('date_deadline','&lt;',time.strftime('%Y-%m-%d')),('state','in',('draft','pending','open'))]</field>
<field name="domain">[('date_deadline','&lt;',time.strftime('%Y-%m-%d'))]</field>
<field name="filter" eval="True"/>
<field name="search_view_id" ref="view_task_search_form"/>
</record>
@ -706,7 +686,6 @@
<field name="case_default"/>
</group>
<group>
<field name="state"/>
<field name="sequence"/>
<field name="fold"/>
</group>
@ -723,7 +702,7 @@
<tree string="Task Stage">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="state"/>
<field name="fold"/>
</tree>
</field>
</record>
@ -767,7 +746,9 @@
</record>
<!-- User Form -->
<act_window context="{'search_default_user_id': [active_id], 'default_user_id': active_id}" domain="[('state', '&lt;&gt;', 'cancelled'),('state', '&lt;&gt;', 'done')]" id="act_res_users_2_project_task_opened" name="Assigned Tasks" res_model="project.task" src_model="res.users" view_mode="tree,form,gantt,calendar,graph" view_type="form"/>
<act_window context="{'search_default_user_id': [active_id], 'default_user_id': active_id}"
id="act_res_users_2_project_task_opened" name="Assigned Tasks"
res_model="project.task" src_model="res.users" view_mode="tree,form,gantt,calendar,graph" view_type="form"/>
<!-- Tags -->
<record model="ir.ui.view" id="project_category_search_view">

View File

@ -14,7 +14,6 @@
<field name="user_id"/>
<field name="remaining_hours"/>
<field name="kanban_state"/>
<field name="state" groups="base.group_no_one"/>
</tree>
</field>
</record>
@ -40,8 +39,7 @@
<field name="date"/>
<field name="project_id"/>
<field name="user_id"/>
<filter name="open" string="In Progress Tasks" domain="[('state','in',('open','draft'))]"/>
<filter string="Pending Tasks" domain="[('state','=','pending')]" context="{'show_delegated':False}"/>
<filter name="new" string="New" domain="[('type_id.sequence', '=', 1)]"/>
<separator/>
<filter name="kanban_blocked" string="Blocked" domain="[('kanban_state','=','blocked')]"/>
<filter name="kanban_ready" string="Ready" domain="[('kanban_state','=','done')]"/>

View File

@ -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
""")

View File

@ -13,11 +13,12 @@
<tree string="Tasks Analysis" create="false">
<field name="name" invisible="1"/>
<field name="project_id" invisible="1"/>
<field name="stage_id" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="date_deadline" invisible="1"/>
<field name="state" invisible="1"/>
<field name="date_start" invisible="1"/>
<field name="date_end" invisible="1"/>
<field name="date_last_stage_update" invisible="1"/>
<field name="company_id" invisible="1" groups="base.group_multi_company"/>
<field name="partner_id" invisible="1"/>
<field name="day" invisible="1"/>
@ -44,7 +45,7 @@
<field name="arch" type="xml">
<graph string="Tasks Analysis" type="bar">
<field name="name"/>
<field name="state" group="True"/>
<field name="stage_id" group="True"/>
<field name="no_of_days" operator="+"/>
</graph>
</field>
@ -58,31 +59,29 @@
<field name="date_start"/>
<field name="date_end"/>
<field name="date_deadline"/>
<filter string="New" icon="terp-document-new" domain="[('state','=','draft')]" help = "New tasks"/>
<filter string="In progress" icon="terp-check" domain="[('state', '=' ,'open')]" help = "In progress tasks"/>
<filter string="Pending" icon="terp-gtk-media-pause" domain="[('state','=','pending')]" help = "Pending tasks"/>
<filter string="Done" icon="terp-dialog-close" name="done" domain="[('state','=','done')]"/>
<separator/>
<filter icon="terp-folder-violet" string="My Projects" help="My Projects" domain="[('project_id.user_id','=',uid)]"/>
<separator/>
<filter icon="terp-personal" string="My Task" help = "My tasks" domain="[('user_id','=',uid)]" />
<filter icon="terp-personal-" string="Non Assigned Tasks to users" help="Non Assigned Tasks to users" domain="[('user_id','=',False)]"/>
<field name="date_last_stage_update"/>
<field name="project_id"/>
<field name="user_id"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="stage_id"/>
<filter string="Unassigned" name="unassigned" domain="[('user_id','=',False)]"/>
<filter string="New" name="new" domain="[('stage_id.sequence', '=', 1)]"/>
<separator/>
<filter string="My Task" domain="[('user_id','=',uid)]" />
<group expand="0" string="Extended Filters...">
<field name="priority"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group expand="1" string="Group By...">
<filter string="Project" name="project" icon="terp-folder-violet" context="{'group_by':'project_id'}"/>
<filter string="Task" icon="terp-stock_align_left_24" context="{'group_by':'name'}" />
<filter string="Contact" icon="terp-partner" context="{'group_by':'partner_id'}" />
<filter string="Assigned to" name="User" icon="terp-personal" context="{'group_by':'user_id'}" />
<filter string="Company" icon="terp-go-home" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Day" icon="terp-go-today" context="{'group_by':'day'}" help="Creation Date"/>
<filter string="Month" icon="terp-go-month" context="{'group_by':'month'}" help="Creation Date"/>
<filter string="Year" icon="terp-go-year" context="{'group_by':'year'}" help="Creation Date"/>
<filter string="Project" name="project" context="{'group_by':'project_id'}"/>
<filter string="Task" context="{'group_by':'name'}" />
<filter string="Contact" context="{'group_by':'partner_id'}" />
<filter string="Assigned to" name="User" context="{'group_by':'user_id'}" />
<filter string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Day" context="{'group_by':'day'}" help="Creation Date"/>
<filter string="Month" context="{'group_by':'month'}" help="Creation Date"/>
<filter string="Year" context="{'group_by':'year'}" help="Creation Date"/>
<filter string="Last Stage Update" context="{'group_by':'date_last_stage_update'}" help="Last Stage Update"/>
</group>
</search>
</field>

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
19 access_project_task_history_cumulative project.task.history project project.model_project_task_history_cumulative project.group_project_manager 1 0 0 0
20 access_project_task_history_cumulative project.task.history project project.model_project_task_history_cumulative project.group_project_user 1 0 0 0
21 access_resource_calendar project.resource_calendar manager resource.model_resource_calendar project.group_project_manager 1 0 0 0
22 access_resource_calendar_leaves_user resource.calendar.leaves user resource.model_resource_calendar_leaves project.group_project_user 1 1 1 1
23 access_project_category project.project_category model_project_category 1 0 0 0
24 access_project_category_manager project.project_category model_project_category project.group_project_manager 1 1 1 1
25 access_mail_alias mail.alias mail.model_mail_alias project.group_project_manager 1 1 1 1

View File

@ -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

View File

@ -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")])

View File

@ -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")])

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_project_flow
checks = [
test_project_flow,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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})

View File

@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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: <whatever-2a840@postmaster.twitter.com>
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 <patrick.ratatouille@agrolait.com>',
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')

View File

@ -20,7 +20,5 @@
##############################################################################
import project_task_delegate
import project_task_reevaluate
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,84 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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:

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_project_task_reevaluate" model="ir.ui.view">
<field name="name">Re-evaluate Task</field>
<field name="model">project.task.reevaluate</field>
<field name="arch" type="xml">
<form string="Reevaluate Task" version="7.0">
<separator string="Reevaluation Task"/>
<group>
<field name="remaining_hours" widget="float_time"/>
</group>
<footer>
<button name="compute_hours" string="_Evaluate" type="object" default_focus="1" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_project_task_reevaluate" model="ir.actions.act_window">
<field name="name">Re-evaluate Task</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">project.task.reevaluate</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>

View File

@ -79,7 +79,6 @@
<field name="remaining_hours" position="after">
<field string="Timeframe" name="timebox_id" invisible=" not context.get('gtd', False)"/>
<field name="context_id" invisible="not context.get('context_show', False)" widget="selection"/>
<button name="do_reopen" states="done,cancelled" string="Reactivate" type="object" icon="gtk-convert" help="For reopening the tasks" invisible="not context.get('set_visible',False)"/>
</field>
</field>
</record>
@ -103,16 +102,17 @@
<field name="arch" type="xml">
<search string="My Tasks">
<field name="name" string="My Tasks"/>
<filter name="open" string="In Progress" domain="[('state','in',('draft','open'))]" help="In Progress and draft tasks" icon="terp-camera_test"/>
<filter string="Pending" domain="[('state','=','pending')]" context="{'show_delegated':False}" help="Pending Tasks" icon="terp-gtk-media-pause"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter name="message_unread" string="Unread Messages" domain="[('message_unread','=',True)]"/>
<separator/>
<filter string="Inbox" domain="[('timebox_id','=', False)]" help="Tasks having no timebox assigned yet"/>
<filter string="No Timebox" domain="[('timebox_id', '=', False)]" help="Tasks having no timebox assigned yet"/>
<group expand="0" string="Display">
<filter string="Show Context" name="context_show" context="{'context_show': True}" domain="[]" icon="terp-camera_test" help="Show the context field"/>
<filter string="Show Deadlines" context="{'deadline_visible': False}" domain="[]" help="Show only tasks having a deadline" icon="terp-gnome-cpu-frequency-applet+"/>
</group>
<group expand="0" string="Group By...">
<filter string="Stage" name="group_stage_id" context="{'group_by':'stage_id'}"/>
<filter string="Timebox" name="group_timebox_id" context="{'group_by':'timebox_id'}"/>
</group>
</search>
</field>
</record>

View File

@ -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)

View File

@ -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,

View File

@ -6,7 +6,7 @@
<field name="name">Project Issue Board Tree</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<tree string="Issue Tracker Tree" colors="black:state=='open';blue:state=='pending';grey:state in ('cancel', 'done')">
<tree string="Issue Tracker Tree">
<field name="id"/>
<field name="create_date"/>
<field name="name"/>
@ -16,7 +16,6 @@
<field name="version_id" widget="selection"/>
<field name="progress" widget="progressbar" attrs="{'invisible':[('task_id','=',False)]}"/>
<field name="stage_id" widget="selection" readonly="1"/>
<field name="state" invisible="1"/>
<field name="categ_ids" invisible="1"/>
<field name="task_id" invisible="1"/>
</tree>
@ -28,7 +27,7 @@
<field name="res_model">project.issue</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('state','not in',('cancel','done')),('user_id','=',uid)]</field>
<field name="domain">[('user_id', '=', uid)]</field>
<field name="view_id" ref="project_issue_board_tree_view"/>
</record>

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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 <b>converted</b> 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

View File

@ -35,11 +35,11 @@ Access all issues from the top Project menu, and access the issues of a specific
<field name="default" eval="False"/>
<field name="description">Issue created</field>
</record>
<record id="mt_issue_started" model="mail.message.subtype">
<field name="name">Issue Started</field>
<record id="mt_issue_assigned" model="mail.message.subtype">
<field name="name">Issue Assigned</field>
<field name="res_model">project.issue</field>
<field name="default" eval="False"/>
<field name="description">Issue started</field>
<field name="description">Issue assigned</field>
</record>
<record id="mt_issue_blocked" model="mail.message.subtype">
<field name="name">Issue Blocked</field>
@ -47,12 +47,6 @@ Access all issues from the top Project menu, and access the issues of a specific
<field name="default" eval="False"/>
<field name="description">Issue blocked</field>
</record>
<record id="mt_issue_closed" model="mail.message.subtype">
<field name="name">Issue Closed</field>
<field name="res_model">project.issue</field>
<field name="default" eval="False"/>
<field name="description">Issue closed</field>
</record>
<record id="mt_issue_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field>
<field name="res_model">project.issue</field>
@ -67,11 +61,11 @@ Access all issues from the top Project menu, and access the issues of a specific
<field name="parent_id" eval="ref('mt_issue_new')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_started" model="mail.message.subtype">
<field name="name">Issue Started</field>
<record id="mt_project_issue_assigned" model="mail.message.subtype">
<field name="name">Issue Assigned</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_issue_started')"/>
<field name="parent_id" eval="ref('mt_issue_assigned')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_blocked" model="mail.message.subtype">
@ -80,12 +74,6 @@ Access all issues from the top Project menu, and access the issues of a specific
<field name="parent_id" eval="ref('mt_issue_blocked')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_closed" model="mail.message.subtype">
<field name="name">Issue Closed</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_issue_closed')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_stage" model="mail.message.subtype">
<field name="name">Issue Stage Changed</field>
<field name="res_model">project.project</field>

View File

@ -48,12 +48,6 @@
<field name="arch" type="xml">
<form string="Issue" version="7.0">
<header>
<button name="case_close" string="Done" type="object"
states="open" groups="base.group_user"/>
<button name="case_close" string="Done" type="object"
states="draft,pending" groups="base.group_user"/>
<button name="case_cancel" string="Cancel Issue" type="object"
states="draft,open,pending" groups="base.group_user"/>
<field name="stage_id" widget="statusbar" clickable="True"/>
</header>
<sheet string="Issue">
@ -76,7 +70,7 @@
<label for="project_id"/>
<div>
<field name="project_id" on_change="on_change_project(project_id)" class="oe_inline" context="{'default_use_issues':1}"/>
<button name="case_escalate" string="⇒ Escalate" type="object" states="draft,open,pending" class="oe_link"
<button name="case_escalate" string="⇒ Escalate" type="object" class="oe_link"
groups="base.group_user"/>
</div>
</group>
@ -106,7 +100,6 @@
</group>
<group string="Status" groups="base.group_no_one">
<field name="active"/>
<field name="state" string="Status"/>
</group>
</page>
</notebook>
@ -123,7 +116,7 @@
<field name="name">Project Issue Tracker Tree</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<tree string="Issue Tracker Tree" fonts="bold:message_unread==True" colors="black:state=='open';blue:state=='pending';grey:state in ('cancel', 'done')">
<tree string="Issue Tracker Tree" fonts="bold:message_unread==True">
<field name="message_unread" invisible="1"/>
<field name="id"/>
<field name="name"/>
@ -135,7 +128,6 @@
<field name="user_id"/>
<field name="progress" widget="progressbar" attrs="{'invisible':[('task_id','=',False)]}"/>
<field name="stage_id" widget="selection" readonly="1"/>
<field name="state" invisible="1"/>
<field name="categ_ids" invisible="1"/>
<field name="task_id" invisible="1"/>
</tree>
@ -149,17 +141,16 @@
<search string="Issue Tracker Search">
<field name="name" string="Issue" filter_domain="['|', '|', '|', ('partner_id','child_of',self), ('description','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/>
<field name="id"/>
<filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
<filter string="New" name="draft" domain="[('stage_id.sequence', '=', 1)]"/>
<separator/>
<filter name="filter_new" string="New" icon="terp-document-new" domain="[('state','=','draft')]" help="New Issues"/>
<filter name="filter_open" string="To Do" domain="[('state','=','open')]" help="To Do Issues" icon="terp-check"/>
<separator/>
<filter string="Unassigned Issues" domain="[('user_id','=',False)]" help="Unassigned Issues" icon="terp-personal-"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<field name="user_id"/>
<field name="project_id"/>
<field name="categ_ids"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="stage_id" domain="[]"/>
<group expand="0" string="Group By..." >
<filter string="Responsible" name="group_user_id" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Contact" name="group_partner_id" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>
@ -269,7 +260,7 @@
<field name="name">Project Issue- Feature Tracker Tree</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<tree string="Feature Tracker Tree" fonts="bold:message_unread==True" colors="red:state=='open';black:state in ('draft', 'cancel','done','pending')">
<tree string="Feature Tracker Tree" fonts="bold:message_unread==True">
<field name="id"/>
<field name="message_unread" invisible="1"/>
<field name="name" string="Feature description"/>
@ -278,7 +269,6 @@
<field name="version_id"/>
<field name="user_id"/>
<field name="stage_id" widget="selection" readonly="1"/>
<field name="state" groups="base.group_no_one"/>
</tree>
</field>
</record>

View File

@ -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,

View File

@ -20,7 +20,7 @@
<field name="partner_id" invisible="1"/>
<field name="task_id" invisible="1"/>
<field name="date_closed" invisible="1"/>
<field name="state" invisible="1"/>
<field name="date_last_stage_update" invisible="1"/>
<field name="day" invisible="1"/>
<field name="nbr" string="#Project Issues" sum="#Number of Project Issues"/>
<field name="delay_open" avg="Avg Opening Delay"/>
@ -36,9 +36,9 @@
<field name="model">project.issue.report</field>
<field name="arch" type="xml">
<graph orientation="horizontal" string="Project Issue" type="bar">
<field name="state"/>
<field name="nbr" operator="+"/>
<field group="True" name="user_id"/>
<field name="user_id" group="True"/>
<field name="stage_id" group="True"/>
</graph>
</field>
</record>
@ -49,14 +49,15 @@
<field name="arch" type="xml">
<search string="Search">
<field name="creation_date"/>
<filter icon="terp-camera_test" string="New" domain="[('state','=','draft')]"/>
<filter icon="terp-check" string="To Do" domain="[('state','=','open')]"/>
<filter icon="terp-gtk-media-pause" string="Pending" domain="[('state','=','pending')]"/>
<filter icon="terp-dialog-close" string="Done" domain="[('state','=','done')]"/>
<field name="project_id"/>
<field name="user_id"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="version_id"/>
<field name="stage_id"/>
<filter string="Unassigned" name="unassigned" domain="[('user_id','=',False)]"/>
<filter string="New" name="new" domain="[('stage_id.sequence', '=', 1)]"/>
<filter string="Done" name="done" domain="[('stage_id.fold', '=', True)]"
help="Tasks beloging to a folded stage"/>
<group expand="1" string="Group By...">
<filter string="Assigned to" name="Responsible" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}" />
<filter string="Contact" icon="terp-partner" context="{'group_by':'partner_id'}" />
@ -70,6 +71,7 @@
<filter string="Day" icon="terp-go-today" domain="[]" context="{'group_by':'day'}" help="Creation Date"/>
<filter string="Month" icon="terp-go-month" domain="[]" context="{'group_by':'month'}" help="Creation Date"/>
<filter string="Year" icon="terp-go-year" domain="[]" context="{'group_by':'name'}" help="Creation Date"/>
<filter string="Last Stage Update" context="{'group_by':'date_last_stage_update'}"/>
</group>
</search>
</field>

View File

@ -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'

View File

@ -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'

View File

@ -200,7 +200,7 @@
<field name="project_id" invisible="1"/>
<field name="total_hours" sum='Total Hours'/>
<field name="remaining_hours" widget="float_time" sum="Remaining Hours"/>
<field name="state"/>
<field name="stage_id"/>
</tree>
</field>
</page>

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
4 access_project_phase_manager project.phase manager model_project_phase project.group_project_manager 1 1 1 1
5 access_project_user_allocation_manager project.user.allocation manager model_project_user_allocation project.group_project_manager 1 1 1 1
6 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
7 access_resource_resource_manager user.user manager resource.model_resource_resource project.group_project_manager 1 1 1 1
8 access_project_user_allocation_manager project.user.allocation.manager model_project_user_allocation project.group_project_manager 1 1 1 1
9 access_project_resource_calendar_attendance resource.calendar.attendance resource.model_resource_calendar_attendance project.group_project_manager 1 0 0 0

View File

@ -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"

View File

@ -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):

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<!--Resource: sale.order.line for services type product-->
<record id="line_services" model="sale.order.line">
@ -14,5 +13,8 @@
<field name="type">make_to_order</field>
</record>
<record id="project.project_tt_deployment" model="project.task.type">
<field name="closed" eval="True"/>
</record>
</data>
</openerp>

View File

@ -21,14 +21,24 @@
</field>
</field>
</record>
<record id="task_type_edit_mrp_inherit" model="ir.ui.view">
<field name="name">project.task.type.mrp.inherit</field>
<field name="model">project.task.type</field>
<field name="inherit_id" ref="project.task_type_edit"/>
<field name="arch" type="xml">
<field name="case_default" position="after">
<field name="closed"/>
</field>
</field>
</record>
<record id="view_project_mrp_inherit_form2" model="ir.ui.view">
<field name="name">project.mrp.form.view.inherit</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<xpath expr="//page[@string='Extra Info']//field[@name='state']" position="after">
<field name="company_id" position="before">
<field name="sale_line_id" string="Order Line"/>
</xpath>
</field>
</field>
</record>
<record id="product_product_normal_form_supply_view" model="ir.ui.view">

View File

@ -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

View File

@ -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."

View File

@ -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
-