[MERGE] Sync with trunk
bzr revid: tde@openerp.com-20130719100119-ohpzxw4r1fxta93k
This commit is contained in:
commit
163d88ea32
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -0,0 +1,22 @@
|
|||
=====================
|
||||
Project DevDoc
|
||||
=====================
|
||||
|
||||
Project module documentation
|
||||
===================================
|
||||
|
||||
Documentation topics
|
||||
''''''''''''''''''''
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
stage_status.rst
|
||||
|
||||
Changelog
|
||||
'''''''''
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
changelog.rst
|
|
@ -0,0 +1,55 @@
|
|||
.. _stage_status:
|
||||
|
||||
Stage and Status
|
||||
================
|
||||
|
||||
.. versionchanged:: 8.0 saas-2 state/stage cleaning
|
||||
|
||||
Stage
|
||||
+++++
|
||||
|
||||
This revision removed the concept of state on project.task objects. The ``state``
|
||||
field has been totally removed and replaced by stages, using ``stage_id``. The
|
||||
following models are impacted:
|
||||
|
||||
- ``project.task`` now use only stages. However a convention still exists about
|
||||
'New' stage. A task is consdered as ``new`` when it has the following
|
||||
properties:
|
||||
|
||||
- ``stage_id and stage_id.sequence = 1``
|
||||
|
||||
- ``project.task.type`` do not have any ``state`` field anymore.
|
||||
- ``project.task.report`` do not have any ``state`` field anymore.
|
||||
|
||||
By default a newly created task is in a new stage. It means that it will
|
||||
fetch the stage having ``sequence = 1``. Stage mangement is done using the
|
||||
kanban view or the clikable statusbar. It is not done using buttons anymore.
|
||||
|
||||
Stage analysis
|
||||
++++++++++++++
|
||||
|
||||
Stage analysis can be performed using the newly introduced ``date_last_stage_update``
|
||||
datetime field. This field is updated everytime ``stage_id`` is updated.
|
||||
|
||||
``project.task.report`` model also uses the ``date_last_stage_update`` field.
|
||||
This allows to group and analyse the time spend in the various stages.
|
||||
|
||||
Open / Assignation date
|
||||
+++++++++++++++++++++++
|
||||
|
||||
The ``date_open`` field meaning has been updated. It is now set when the ``user_id``
|
||||
(responsible) is set. It is therefore the assignation date.
|
||||
|
||||
Subtypes
|
||||
++++++++
|
||||
|
||||
The following subtypes are triggered on ``project.task``:
|
||||
|
||||
- ``mt_task_new``: new tasks. Condition: ``obj.stage_id and obj.stage_id.sequence == 1``
|
||||
- ``mt_task_stage``: stage changed. Condition: ``obj.stage_id and obj.stage_id.sequence != 1``
|
||||
- ``mt_task_assigned``: user assigned. condition: ``obj.user_id and obj.user_id.id``
|
||||
- ``mt_task_blocked``: kanban state blocked. Condition: ``obj.kanban_state == 'blocked'``
|
||||
|
||||
|
||||
Those subtypes are also available on the ``project.project`` model and are used
|
||||
for the auto subscription.
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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','<>',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<current_date) and (state in ('draft','pending','open'))" string="Tasks">
|
||||
<tree fonts="bold:message_unread==True" colors="red:date_deadline and (date_deadline<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','<',time.strftime('%Y-%m-%d')),('state','in',('draft','pending','open'))]</field>
|
||||
<field name="domain">[('date_deadline','<',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', '<>', 'cancelled'),('state', '<>', '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">
|
||||
|
|
|
@ -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')]"/>
|
||||
|
|
|
@ -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
|
||||
""")
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,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
|
|
@ -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")])
|
|
@ -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")])
|
|
@ -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:
|
|
@ -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})
|
|
@ -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')
|
|
@ -20,7 +20,5 @@
|
|||
##############################################################################
|
||||
|
||||
import project_task_delegate
|
||||
import project_task_reevaluate
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -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:
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'
|
|
@ -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'
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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."
|
|
@ -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
|
||||
-
|
||||
|
|
Loading…
Reference in New Issue