[MERGE]Trunk.

bzr revid: vme@tinyerp.com-20130823050602-33bqv07e7g8dhv7x
This commit is contained in:
Vidhin Mehta
2013-08-23 10:36:02 +05:30
940 changed files with 75863 additions and 30626 deletions
-5
View File
@@ -40,7 +40,6 @@
],
'depends': [
'base_setup',
'base_status',
'product',
'analytic',
'board',
@@ -66,7 +65,6 @@ Dashboard / Reports for Project Management will include:
'data': [
'security/project_security.xml',
'wizard/project_task_delegate_view.xml',
'wizard/project_task_reevaluate_view.xml',
'security/ir.model.access.csv',
'project_data.xml',
'project_view.xml',
@@ -79,9 +77,6 @@ Dashboard / Reports for Project Management will include:
],
'demo': ['project_demo.xml'],
'test': [
'test/project_demo.yml',
'test/project_process.yml',
'test/task_process.yml',
],
'installable': True,
'auto_install': False,
+1 -2
View File
@@ -16,7 +16,6 @@
<field name="effective_hours" widget="float_time"/>
<field name="progress" widget="progressbar"/>
<field name="stage_id" invisible="context.get('set_visible',False)"/>
<field name="state" invisible="1"/>
</tree>
</field>
</record>
@@ -26,7 +25,7 @@
<field name="res_model">project.task</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('user_id','=',uid),('state','not in',('cancel','done'))]</field>
<field name="domain">[('user_id', '=', uid), ('stage_id.fold', '!=', True)]</field>
<field name="view_id" ref="view_task_tree"/>
</record>
+16
View File
@@ -0,0 +1,16 @@
.. _changelog:
Changelog
=========
`trunk (saas-2)`
----------------
- Stage/state update
- ``project.task``: removed inheritance from ``base_stage`` class and removed
``state`` field. Added ``date_last_stage_update`` field holding last stage_id
modification. Updated reports.
- ``project.task.type``: removed ``state`` field.
- Removed ``project.task.reevaluate`` wizard.
+22
View File
@@ -0,0 +1,22 @@
=====================
Project DevDoc
=====================
Project module documentation
===================================
Documentation topics
''''''''''''''''''''
.. toctree::
:maxdepth: 1
stage_status.rst
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst
+55
View File
@@ -0,0 +1,55 @@
.. _stage_status:
Stage and Status
================
.. versionchanged:: 8.0 saas-2 state/stage cleaning
Stage
+++++
This revision removed the concept of state on project.task objects. The ``state``
field has been totally removed and replaced by stages, using ``stage_id``. The
following models are impacted:
- ``project.task`` now use only stages. However a convention still exists about
'New' stage. A task is consdered as ``new`` when it has the following
properties:
- ``stage_id and stage_id.sequence = 1``
- ``project.task.type`` do not have any ``state`` field anymore.
- ``project.task.report`` do not have any ``state`` field anymore.
By default a newly created task is in a new stage. It means that it will
fetch the stage having ``sequence = 1``. Stage mangement is done using the
kanban view or the clikable statusbar. It is not done using buttons anymore.
Stage analysis
++++++++++++++
Stage analysis can be performed using the newly introduced ``date_last_stage_update``
datetime field. This field is updated everytime ``stage_id`` is updated.
``project.task.report`` model also uses the ``date_last_stage_update`` field.
This allows to group and analyse the time spend in the various stages.
Open / Assignation date
+++++++++++++++++++++++
The ``date_open`` field meaning has been updated. It is now set when the ``user_id``
(responsible) is set. It is therefore the assignation date.
Subtypes
++++++++
The following subtypes are triggered on ``project.task``:
- ``mt_task_new``: new tasks. Condition: ``obj.stage_id and obj.stage_id.sequence == 1``
- ``mt_task_stage``: stage changed. Condition: ``obj.stage_id and obj.stage_id.sequence != 1``
- ``mt_task_assigned``: user assigned. condition: ``obj.user_id and obj.user_id.id``
- ``mt_task_blocked``: kanban state blocked. Condition: ``obj.kanban_state == 'blocked'``
Those subtypes are also available on the ``project.project`` model and are used
for the auto subscription.
+65 -46
View File
@@ -7,19 +7,19 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2012-12-14 14:12+0000\n"
"Last-Translator: sum1201 <Unknown>\n"
"PO-Revision-Date: 2013-08-13 04:04+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-03-16 04:57+0000\n"
"X-Generator: Launchpad (build 16532)\n"
"X-Launchpad-Export-Date: 2013-08-14 04:54+0000\n"
"X-Generator: Launchpad (build 16723)\n"
#. module: project
#: view:project.project:0
msgid "Email Interface"
msgstr "Email 接口"
msgstr "电子邮件接口"
#. module: project
#: help:account.analytic.account,use_tasks:0
@@ -38,7 +38,7 @@ msgstr "进度"
#. module: project
#: model:process.node,name:project.process_node_taskbydelegate0
msgid "Task by delegate"
msgstr "委派任务"
msgstr "委派的任务"
#. module: project
#: view:project.project:0
@@ -58,18 +58,18 @@ msgstr "阶段名称"
#. module: project
#: model:process.transition.action,name:project.process_transition_action_openpendingtask0
msgid "Set pending"
msgstr "设为未决"
msgstr "设置为等待状态"
#. module: project
#: view:project.project:0
msgid "New Project Based on Template"
msgstr "新的基于模板的项目"
msgstr "基于该模板新建项目"
#. module: project
#: view:report.project.task.user:0
#: field:report.project.task.user,day:0
msgid "Day"
msgstr "天数"
msgstr "日"
#. module: project
#: model:project.task.type,name:project.project_tt_merge
@@ -82,7 +82,7 @@ msgid "Start Task"
msgstr "开始任务"
#. module: project
#: code:addons/project/project.py:932
#: code:addons/project/project.py:944
#, python-format
msgid "Warning !"
msgstr "警告!"
@@ -91,7 +91,7 @@ msgstr "警告!"
#: help:project.project,message_unread:0
#: help:project.task,message_unread:0
msgid "If checked new messages require your attention."
msgstr "如果要求你关注新消息,勾选此项"
msgstr "如果选中此项,你需要注意新的消息。"
#. module: project
#: model:process.node,name:project.process_node_donetask0
@@ -101,12 +101,12 @@ msgstr "完成任务"
#. module: project
#: model:process.node,note:project.process_node_donetask0
msgid "Task is Completed"
msgstr "任务完成"
msgstr "任务已经完成"
#. module: project
#: view:res.partner:0
msgid "False"
msgstr "否"
msgstr "错误的"
#. module: project
#: model:project.task.type,name:project.project_tt_testing
@@ -121,7 +121,7 @@ msgstr "辅助核算科目"
#. module: project
#: field:project.config.settings,group_time_work_estimation_tasks:0
msgid "Manage time estimation on tasks"
msgstr "估计管理任务的时间"
msgstr "管理任务的估计时间"
#. module: project
#: help:project.project,message_summary:0
@@ -129,11 +129,11 @@ msgstr "估计管理任务的时间"
msgid ""
"Holds the Chatter summary (number of messages, ...). This summary is "
"directly in html format in order to be inserted in kanban views."
msgstr "保存复杂的摘要(消息数量,……等)。为了插入到看板视图,这一摘要直接是是HTML格式。"
msgstr "保存对话概要信息,这些概要信息将为直接以HTML格式插入到看板视图。"
#. module: project
#: code:addons/project/project.py:432
#: code:addons/project/project.py:1318
#: code:addons/project/project.py:444
#: code:addons/project/project.py:1332
#, python-format
msgid "Warning!"
msgstr "警告!"
@@ -163,12 +163,12 @@ msgstr "重设项目"
#. module: project
#: view:report.project.task.user:0
msgid "In progress tasks"
msgstr "进行中任务"
msgstr "进行中的任务"
#. module: project
#: help:project.project,progress_rate:0
msgid "Percent of tasks closed according to the total of tasks todo."
msgstr "结束任务和全部要做任务的百分比"
msgstr "已结束任务和未结束任务的百分比。"
#. module: project
#: model:ir.actions.client,name:project.action_client_project_menu
@@ -224,7 +224,7 @@ msgstr "分配给"
#: model:mail.message.subtype,name:project.mt_project_task_closed
#: model:mail.message.subtype,name:project.mt_task_closed
msgid "Task Done"
msgstr ""
msgstr "已完成的任务"
#. module: project
#: view:project.project:0
@@ -265,7 +265,7 @@ msgstr "任务分派"
#: model:mail.message.subtype,name:project.mt_project_task_started
#: model:mail.message.subtype,name:project.mt_task_started
msgid "Task Started"
msgstr ""
msgstr "已开始的任务"
#. module: project
#: view:project.task:0
@@ -383,7 +383,7 @@ msgid "Assigned to"
msgstr "指派到"
#. module: project
#: code:addons/project/project.py:1021
#: code:addons/project/project.py:1033
#, python-format
msgid "Delegated User should be specified"
msgstr "应该制定委派用户"
@@ -433,7 +433,7 @@ msgstr "使用集成的协作记事簿任务"
#: model:mail.message.subtype,name:project.mt_project_task_blocked
#: model:mail.message.subtype,name:project.mt_task_blocked
msgid "Task Blocked"
msgstr ""
msgstr "延期的任务"
#. module: project
#: model:process.node,note:project.process_node_opentask0
@@ -453,12 +453,12 @@ msgstr "oe_kanban_text_red"
#. module: project
#: model:mail.message.subtype,description:project.mt_task_blocked
msgid "Task blocked"
msgstr ""
msgstr "延期的任务"
#. module: project
#: view:project.task:0
msgid "Delegation"
msgstr "代表"
msgstr "任务委派"
#. module: project
#: field:project.task,create_date:0
@@ -636,6 +636,15 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 创建一个新项目\n"
" </p><p>\n"
" "
"项目是组织活动,计划任务,跟踪问题,记录人力资源成本的组织形式。项目可以分为内部项目(研发活动,加快销售进度),私有项目或者客户参与的项目。\n"
" </p><p>\n"
" 内部用户以项目成员的方式参与到项目中,也可以邀请客户加入到项目中分享项目活动。\n"
" </p>\n"
" "
#. module: project
#: view:project.config.settings:0
@@ -647,7 +656,7 @@ msgstr "计划"
#: field:project.task,date_deadline:0
#: field:report.project.task.user,date_deadline:0
msgid "Deadline"
msgstr "最后期限"
msgstr "计划完成日期"
#. module: project
#: view:project.task.history.cumulative:0
@@ -740,7 +749,7 @@ msgstr "待处理任务草稿"
#. module: project
#: field:project.project,alias_model:0
msgid "Alias Model"
msgstr ""
msgstr "别名模型"
#. module: project
#: help:report.project.task.user,closing_days:0
@@ -845,7 +854,7 @@ msgstr "增加筛选条件"
#. module: project
#: model:ir.ui.menu,name:project.menu_tasks_config
msgid "GTD"
msgstr ""
msgstr "GTD"
#. module: project
#: help:project.task,state:0
@@ -866,7 +875,7 @@ msgstr "公司"
#. module: project
#: field:project.task.type,fold:0
msgid "Folded by Default"
msgstr ""
msgstr "默认折叠"
#. module: project
#: field:project.task.history,date:0
@@ -1137,7 +1146,7 @@ msgstr "您的文档的状态将自动更改选定的阶段。例如,如果某
#. module: project
#: view:project.task:0
msgid "Extra Info"
msgstr "额外信息"
msgstr "其它信息"
#. module: project
#: view:project.task:0
@@ -1195,9 +1204,9 @@ msgid "Computed as: Time Spent + Remaining Time."
msgstr "计算:花费的时间 + 剩余的时间"
#. module: project
#: code:addons/project/project.py:356
#: code:addons/project/project.py:377
#: code:addons/project/project.py:709
#: code:addons/project/project.py:368
#: code:addons/project/project.py:389
#: code:addons/project/project.py:721
#, python-format
msgid "%s (copy)"
msgstr "%s (副本)"
@@ -1205,7 +1214,7 @@ msgstr "%s (副本)"
#. module: project
#: model:mail.message.subtype,name:project.mt_project_task_stage
msgid "Task Stage Changed"
msgstr ""
msgstr "任务阶段已改变"
#. module: project
#: view:project.task:0
@@ -1397,7 +1406,7 @@ msgid ""
msgstr "做任务的估计时间.通常由项目经理在任务草稿阶段设定"
#. module: project
#: code:addons/project/project.py:220
#: code:addons/project/project.py:230
#, python-format
msgid "Attachments"
msgstr "附件"
@@ -1447,7 +1456,7 @@ msgstr "剩余的小时数"
#. module: project
#: model:mail.message.subtype,description:project.mt_task_stage
msgid "Stage changed"
msgstr ""
msgstr "任务阶段已改变"
#. module: project
#: constraint:project.task:0
@@ -1479,7 +1488,7 @@ msgstr "总小时数"
#. module: project
#: model:ir.model,name:project.model_project_config_settings
msgid "project.config.settings"
msgstr ""
msgstr "项目配置"
#. module: project
#: model:project.task.type,name:project.project_tt_development
@@ -1524,7 +1533,7 @@ msgid "Overpassed Tasks"
msgstr "拖期任务"
#. module: project
#: code:addons/project/project.py:932
#: code:addons/project/project.py:944
#, python-format
msgid ""
"Child task still open.\n"
@@ -1556,7 +1565,7 @@ msgstr "委派你的任务给其它用户"
#. module: project
#: model:mail.message.subtype,description:project.mt_task_started
msgid "Task started"
msgstr ""
msgstr "已开始的任务"
#. module: project
#: help:project.task.reevaluate,remaining_hours:0
@@ -1585,7 +1594,7 @@ msgid "CHECK: "
msgstr "CHECK: "
#. module: project
#: code:addons/project/project.py:432
#: code:addons/project/project.py:444
#, python-format
msgid "You must assign members on the project '%s' !"
msgstr "您必须为项目“%s”指定成员!"
@@ -1697,6 +1706,12 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 单击创建新任务。\n"
" </p><p>\n"
"OpenERP 系统的任务管理允许你为了更高效地完成工作而管理任务管道。你可以跟踪任务进度、与他人讨论任务、附加文档附件等。\n"
" </p>\n"
" "
#. module: project
#: field:project.task,date_end:0
@@ -1717,7 +1732,7 @@ msgstr "文档"
#. module: project
#: model:mail.message.subtype,description:project.mt_task_new
msgid "Task created"
msgstr ""
msgstr "任务已创建"
#. module: project
#: view:report.project.task.user:0
@@ -1811,7 +1826,7 @@ msgid "Starting Date"
msgstr "开始日期"
#. module: project
#: code:addons/project/project.py:398
#: code:addons/project/project.py:410
#: model:ir.actions.act_window,name:project.open_view_project_all
#: model:ir.ui.menu,name:project.menu_projects
#: view:project.project:0
@@ -1972,7 +1987,7 @@ msgid "Reevaluation Task"
msgstr "任务重估"
#. module: project
#: code:addons/project/project.py:1318
#: code:addons/project/project.py:1332
#, python-format
msgid "Please delete the project linked with this account first."
msgstr "请删除与此科目关联的项目"
@@ -1981,7 +1996,7 @@ msgstr "请删除与此科目关联的项目"
#: model:mail.message.subtype,name:project.mt_project_task_new
#: model:mail.message.subtype,name:project.mt_task_new
msgid "Task Created"
msgstr ""
msgstr "任务已创建"
#. module: project
#: view:report.project.task.user:0
@@ -2004,7 +2019,7 @@ msgstr "准备好下一个阶段"
#. module: project
#: field:project.task.type,case_default:0
msgid "Default for New Projects"
msgstr ""
msgstr "默认用于新项目"
#. module: project
#: view:project.task:0
@@ -2042,11 +2057,13 @@ msgid ""
"resource allocation.\n"
" This installs the module project_long_term."
msgstr ""
"用于长期项目管理的模块,能够跟踪计划、排程和资源控制。\n"
" 将会安装模块“project_long_term”。"
#. module: project
#: model:mail.message.subtype,description:project.mt_task_closed
msgid "Task closed"
msgstr ""
msgstr "已关闭的任务"
#. module: project
#: selection:report.project.task.user,month:0
@@ -2074,6 +2091,8 @@ msgid ""
"Provides timesheet support for the issues/bugs management in project.\n"
" This installs the module project_issue_sheet."
msgstr ""
"为项目中的问题/错误管理提供时间表支持。\n"
" 将安装模块“project_issue_sheet”。"
#. module: project
#: selection:project.project,privacy_visibility:0
+122 -240
View File
@@ -25,13 +25,10 @@ import time
from openerp import SUPERUSER_ID
from openerp import tools
from openerp.addons.resource.faces import task as Task
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.addons.base_status.base_stage import base_stage
from openerp.addons.resource.faces import task as Task
_TASK_STATE = [('draft', 'New'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done'), ('cancelled', 'Cancelled')]
class project_task_type(osv.osv):
_name = 'project.task.type'
@@ -44,33 +41,18 @@ class project_task_type(osv.osv):
'case_default': fields.boolean('Default for New Projects',
help="If you check this field, this stage will be proposed by default on each new project. It will not assign this stage to existing projects."),
'project_ids': fields.many2many('project.project', 'project_task_type_rel', 'type_id', 'project_id', 'Projects'),
'state': fields.selection(_TASK_STATE, 'Related Status', required=True,
help="The status of your document is automatically changed regarding the selected stage. " \
"For example, if a stage is related to the status 'Close', when your document reaches this stage, it is automatically closed."),
'fold': fields.boolean('Folded by Default',
help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
}
def _get_default_project_id(self, cr, uid, ctx={}):
proj = ctx.get('default_project_id', False)
if type(proj) is int:
return [proj]
return proj
_defaults = {
'sequence': 1,
'state': 'open',
'fold': False,
'case_default': False,
'project_ids': _get_default_project_id
'project_ids': lambda self, cr, uid, ctx=None: self.pool['project.task']._get_default_project_id(cr, uid, context=ctx),
}
_order = 'sequence'
def short_name(name):
"""Keep first word(s) of name to make it small enough
but distinctive"""
if not name: return name
# keep 7 chars + end of the last word
keep_words = name[:7].strip().split()
return ' '.join(name.split()[:len(keep_words)])
class project(osv.osv):
_name = "project.project"
@@ -99,9 +81,9 @@ class project(osv.osv):
def onchange_partner_id(self, cr, uid, ids, part=False, context=None):
partner_obj = self.pool.get('res.partner')
if not part:
return {'value':{}}
val = {}
if not part:
return {'value': val}
if 'pricelist_id' in self.fields_get(cr, uid, context=context):
pricelist = partner_obj.read(cr, uid, part, ['property_product_pricelist'], context=context)
pricelist_id = pricelist.get('property_product_pricelist', False) and pricelist.get('property_product_pricelist')[0] or False
@@ -152,11 +134,13 @@ class project(osv.osv):
cr.execute("""
SELECT project_id, COALESCE(SUM(planned_hours), 0.0),
COALESCE(SUM(total_hours), 0.0), COALESCE(SUM(effective_hours), 0.0)
FROM project_task WHERE project_id IN %s AND state <> 'cancelled'
FROM project_task
LEFT JOIN project_task_type ON project_task.stage_id = project_task_type.id
WHERE project_task.project_id IN %s AND project_task_type.fold = False
GROUP BY project_id
""", (tuple(child_parent.keys()),))
# aggregate results into res
res = dict([(id, {'planned_hours':0.0,'total_hours':0.0,'effective_hours':0.0}) for id in ids])
res = dict([(id, {'planned_hours':0.0, 'total_hours':0.0, 'effective_hours':0.0}) for id in ids])
for id, planned, total, effective in cr.fetchall():
# add the values specific to id to all parent projects of id in the result
while id:
@@ -182,10 +166,10 @@ class project(osv.osv):
_('You cannot delete a project containing tasks. You can either delete all the project\'s tasks and then delete the project or simply deactivate the project.'))
elif proj.alias_id:
alias_ids.append(proj.alias_id.id)
res = super(project, self).unlink(cr, uid, ids, context=context)
res = super(project, self).unlink(cr, uid, ids, context=context)
mail_alias.unlink(cr, uid, alias_ids, context=context)
return res
def _get_attached_docs(self, cr, uid, ids, field_name, arg, context):
res = {}
attachment = self.pool.get('ir.attachment')
@@ -196,7 +180,7 @@ class project(osv.osv):
task_attachments = attachment.search(cr, uid, [('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)], context=context, count=True)
res[id] = (project_attachments or 0) + (task_attachments or 0)
return res
def _task_count(self, cr, uid, ids, field_name, arg, context=None):
if context is None:
context = {}
@@ -209,22 +193,21 @@ class project(osv.osv):
return res
def _get_alias_models(self, cr, uid, context=None):
"""Overriden in project_issue to offer more options"""
""" Overriden in project_issue to offer more options """
return [('project.task', "Tasks")]
def _get_visibility_selection(self, cr, uid, context=None):
""" Overriden in portal_project to offer more options """
return [('public', 'Public'),
('employees', 'Employees Only'),
('followers', 'Followers Only')]
return [('public', 'Public project'),
('employees', 'Internal project: all employees can access'),
('followers', 'Private project: followers Only')]
def attachment_tree_view(self, cr, uid, ids, context):
task_ids = self.pool.get('project.task').search(cr, uid, [('project_id', 'in', ids)])
domain = [
'|',
'|',
'&', ('res_model', '=', 'project.project'), ('res_id', 'in', ids),
'&', ('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)
]
'&', ('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)]
res_id = ids and ids[0] or False
return {
'name': _('Attachments'),
@@ -237,6 +220,7 @@ class project(osv.osv):
'limit': 80,
'context': "{'default_res_model': '%s','default_res_id': %d}" % (self._name, res_id)
}
# Lambda indirection method to avoid passing a copy of the overridable method when declaring the field
_alias_models = lambda self, *args, **kwargs: self._get_alias_models(*args, **kwargs)
_visibility_selection = lambda self, *args, **kwargs: self._get_visibility_selection(*args, **kwargs)
@@ -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)
@@ -370,13 +347,11 @@ class project(osv.osv):
default['state'] = 'open'
default['line_ids'] = []
default['tasks'] = []
default.pop('alias_name', None)
default.pop('alias_id', None)
proj = self.browse(cr, uid, id, context=context)
if not default.get('name', False):
default.update(name=_("%s (copy)") % (proj.name))
res = super(project, self).copy(cr, uid, id, default, context)
self.map_tasks(cr,uid,id,res,context)
self.map_tasks(cr, uid, id, res, context=context)
return res
def duplicate_template(self, cr, uid, ids, context=None):
@@ -450,7 +425,7 @@ class project(osv.osv):
for project in projects:
if (not project.members) and force_members:
raise osv.except_osv(_('Warning!'),_("You must assign members on the project '%s' !") % (project.name,))
raise osv.except_osv(_('Warning!'),_("You must assign members on the project '%s'!") % (project.name,))
resource_pool = self.pool.get('resource.resource')
@@ -461,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
@@ -527,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,))
@@ -542,28 +515,20 @@ 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!
context = dict(context, project_creation_in_progress=True)
mail_alias = self.pool.get('mail.alias')
if not vals.get('alias_id') and vals.get('name', False):
alias_name = vals.pop('alias_name', None) # prevent errors during copy()
alias_id = mail_alias.create_unique_alias(cr, uid,
# Using '+' allows using subaddressing for those who don't
# have a catchall domain setup.
{'alias_name': alias_name or "project+"+short_name(vals['name'])},
model_name=vals.get('alias_model', 'project.task'),
context=context)
vals['alias_id'] = alias_id
if vals.get('type', False) not in ('template','contract'):
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)
if vals.get('type', False) not in ('template', 'contract'):
vals['type'] = 'contract'
project_id = super(project, self).create(cr, uid, vals, context)
mail_alias.write(cr, uid, [vals['alias_id']], {'alias_defaults': {'project_id': project_id} }, context)
project_id = super(project, self).create(cr, uid, vals, context=create_context)
project_rec = self.browse(cr, uid, project_id, context=context)
self.pool.get('mail.alias').write(cr, uid, [project_rec.alias_id.id], {'alias_parent_thread_id': project_id, 'alias_defaults': {'project_id': project_id}}, context)
return project_id
def write(self, cr, uid, ids, vals, context=None):
@@ -573,26 +538,35 @@ 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']
_mail_post_access = 'read'
_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
'project.mt_task_blocked': lambda self, cr, uid, obj, ctx=None: obj['kanban_state'] == 'blocked',
'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',
},
}
def _get_default_partner(self, cr, uid, context=None):
project_id = self._get_default_project_id(cr, uid, context)
if project_id:
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 False
def _get_default_project_id(self, cr, uid, context=None):
""" Gives default section by checking if present in the context """
return (self._resolve_project_id_from_context(cr, uid, context=context) or False)
@@ -600,14 +574,15 @@ 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'
context key, or None if it cannot be resolved to a single
project.
"""
if context is None: context = {}
if context is None:
context = {}
if type(context.get('default_project_id')) in (int, long):
return context['default_project_id']
if isinstance(context.get('default_project_id'), basestring):
@@ -676,27 +651,32 @@ 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):
if not project_id:
return {}
data = self.pool.get('project.project').browse(cr, uid, [project_id])
partner_id=data and data[0].partner_id
if partner_id:
return {'value':{'partner_id':partner_id.id}}
def onchange_project(self, cr, uid, id, project_id, context=None):
if project_id:
project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
if project and project.partner_id:
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)
@@ -754,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',
@@ -769,10 +742,12 @@ class task(base_stage, osv.osv):
" * Blocked indicates something is preventing the progress of this task\n"
" * Ready for next stage indicates the task is ready to be pulled to the next stage",
readonly=True, required=False),
'create_date': fields.datetime('Create Date', readonly=True,select=True),
'create_date': fields.datetime('Create Date', readonly=True, select=True),
'write_date': fields.datetime('Last Modification Date', readonly=True, select=True), #not displayed in the view but it might be useful with base_action_rule module (and it needs to be defined first for that)
'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'),
@@ -789,7 +764,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),
@@ -812,13 +787,15 @@ 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,
'sequence': 10,
'active': True,
'user_id': lambda obj, cr, uid, context: uid,
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=c),
'user_id': lambda obj, cr, uid, ctx=None: uid,
'company_id': lambda self, cr, uid, ctx=None: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=ctx),
'partner_id': lambda self, cr, uid, ctx=None: self._get_default_partner(cr, uid, context=ctx),
}
_order = "priority, sequence, date_start, name, id"
@@ -938,7 +915,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)
@@ -955,82 +932,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']:
raise osv.except_osv(_("Warning !"), _("Child task still open.\nPlease cancel or complete child task first."))
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)
@@ -1051,6 +956,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])],
@@ -1065,16 +971,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
@@ -1109,50 +1011,56 @@ 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 = {}
if not vals.get('stage_id'):
ctx = context.copy()
if vals.get('project_id'):
ctx['default_project_id'] = vals['project_id']
vals['stage_id'] = self._get_default_stage_id(cr, uid, context=ctx)
# 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)
task_id = super(task, self).create(cr, uid, vals, context=create_context)
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]
if vals.get('project_id'):
project_id = self.pool.get('project.project').browse(cr, uid, vals.get('project_id'), context=context)
if project_id:
vals.setdefault('message_follower_ids', [])
vals['message_follower_ids'] += [(6, 0,[follower.id]) for follower in project_id.message_follower_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
@@ -1168,7 +1076,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():
@@ -1199,17 +1107,6 @@ class task(base_stage, osv.osv):
return [task.project_id.message_get_reply_to()[0] if task.project_id else False
for task in self.browse(cr, uid, ids, context=context)]
def check_mail_message_access(self, cr, uid, mids, operation, model_obj=None, context=None):
""" mail.message document permission rule: can post a new message if can read
because of portal document. """
if not model_obj:
model_obj = self
if operation == 'create':
model_obj.check_access_rights(cr, uid, 'read')
model_obj.check_access_rule(cr, uid, mids, 'read', context=context)
else:
return super(task, self).check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context)
def message_new(self, cr, uid, msg, custom_values=None, context=None):
""" Override to updates the document according to the email. """
if custom_values is None: custom_values = {}
@@ -1238,24 +1135,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"
@@ -1376,7 +1259,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
@@ -1410,14 +1293,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 = {
@@ -1447,7 +1329,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
+14 -34
View File
@@ -30,51 +30,43 @@
<record id="project_tt_analysis" model="project.task.type">
<field name="sequence">1</field>
<field name="name">Analysis</field>
<field name="state">draft</field>
<field name="case_default" eval="False"/>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_specification" model="project.task.type">
<field name="sequence">2</field>
<field name="sequence">10</field>
<field name="name">Specification</field>
<field name="state">pending</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_design" model="project.task.type">
<field name="sequence">2</field>
<field name="sequence">11</field>
<field name="name">Design</field>
<field name="state">open</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_development" model="project.task.type">
<field name="sequence">3</field>
<field name="sequence">12</field>
<field name="name">Development</field>
<field name="state">open</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_testing" model="project.task.type">
<field name="sequence">4</field>
<field name="sequence">13</field>
<field name="name">Testing</field>
<field name="state">open</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_merge" model="project.task.type">
<field name="sequence">5</field>
<field name="sequence">14</field>
<field name="name">Merge</field>
<field name="state">open</field>
<field name="case_default" eval="False"/>
<field name="fold" eval="True"/>
</record>
<record id="project_tt_deployment" model="project.task.type">
<field name="sequence">100</field>
<field name="sequence">20</field>
<field name="name">Done</field>
<field name="state">done</field>
<field name="case_default" eval="True"/>
<field name="fold" eval="True"/>
</record>
<record id="project_tt_cancel" model="project.task.type">
<field name="sequence">200</field>
<field name="sequence">30</field>
<field name="name">Cancelled</field>
<field name="state">cancelled</field>
<field name="case_default" eval="True"/>
<field name="fold" eval="True"/>
</record>
@@ -86,11 +78,11 @@
<field name="default" eval="False"/>
<field name="description">Task created</field>
</record>
<record id="mt_task_started" model="mail.message.subtype">
<field name="name">Task Started</field>
<record id="mt_task_assigned" model="mail.message.subtype">
<field name="name">Task Assigned</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task started</field>
<field name="description">Task Assigned</field>
</record>
<record id="mt_task_blocked" model="mail.message.subtype">
<field name="name">Task Blocked</field>
@@ -98,12 +90,6 @@
<field name="default" eval="False"/>
<field name="description">Task blocked</field>
</record>
<record id="mt_task_closed" model="mail.message.subtype">
<field name="name">Task Done</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task closed</field>
</record>
<record id="mt_task_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field>
<field name="res_model">project.task</field>
@@ -118,11 +104,11 @@
<field name="parent_id" eval="ref('mt_task_new')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_started" model="mail.message.subtype">
<field name="name">Task Started</field>
<record id="mt_project_task_assigned" model="mail.message.subtype">
<field name="name">Task Assigned</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_task_started')"/>
<field name="parent_id" eval="ref('mt_task_assigned')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_blocked" model="mail.message.subtype">
@@ -131,12 +117,6 @@
<field name="parent_id" eval="ref('mt_task_blocked')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_closed" model="mail.message.subtype">
<field name="name">Task Done</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_task_closed')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_stage" model="mail.message.subtype">
<field name="name">Task Stage Changed</field>
<field name="res_model">project.project</field>
+5 -11
View File
@@ -46,9 +46,12 @@
<record id="project_project_2" model="project.project">
<field name="name">Research &amp; Development</field>
<field name="parent_id" ref="all_projects_account"/>
<field name="privacy_visibility">public</field>
<field name="privacy_visibility">followers</field>
<field name="user_id" ref="base.user_demo"/>
<field name="alias_model">project.task</field>
<field name="message_follower_ids" eval="[(6, 0, [
ref('base.user_root'),
ref('base.user_demo')])]"/>
</record>
<!-- We assign after so that default values applies -->
@@ -75,6 +78,7 @@
<field name="parent_id" ref="all_projects_account"/>
<field name="name">Website Design Templates</field>
<field name="user_id" ref="base.user_root"/>
<field name="privacy_visibility">followers</field>
<field name="alias_model">project.task</field>
<field name="privacy_visibility">employees</field>
<field name="members" eval="[(4, ref('base.user_root')), (4,ref('base.user_demo'))]"/>
@@ -221,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"/>
@@ -233,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"/>
@@ -244,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"/>
@@ -278,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"/>
@@ -289,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"/>
@@ -311,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>
@@ -321,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>
@@ -333,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>
@@ -367,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>
@@ -378,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>
+62 -74
View File
@@ -18,28 +18,26 @@
<search string="Tasks">
<field name="name" string="Tasks"/>
<field name="categ_ids"/>
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
<filter string="New" name="draft" domain="[('stage_id.sequence', '=', 1)]"/>
<separator/>
<filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<filter string="My Tasks" domain="[('user_id','=',uid)]"/>
<separator/>
<filter name="draft" string="New" domain="[('state','=','draft')]" help="New Tasks" icon="terp-check"/>
<filter name="open" string="In Progress" domain="[('state','=','open')]" help="In Progress Tasks" icon="terp-camera_test"/>
<filter string="Pending" domain="[('state','=','pending')]" context="{'show_delegated':False}" help="Pending Tasks" icon="terp-gtk-media-pause"/>
<separator/>
<filter name="My project" string="Project" domain="[('project_id.user_id','=',uid)]" help="My Projects" icon="terp-check"/>
<separator/>
<filter string="My Tasks" domain="[('user_id','=',uid)]" help="My Tasks" icon="terp-personal"/>
<filter string="Unassigned Tasks" domain="[('user_id','=',False)]" help="Unassigned Tasks" icon="terp-personal-"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter string="Deadlines" context="{'deadline_visible': False}" domain="[('date_deadline','&lt;&gt;',False)]"
help="Show only tasks having a deadline" icon="terp-gnome-cpu-frequency-applet+"/>
help="Show only tasks having a deadline"/>
<field name="partner_id"/>
<field name="project_id"/>
<field name="user_id"/>
<field name="stage_id" domain="[]"/>
<group expand="0" string="Group By...">
<filter string="Users" name="group_user_id" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Project" name="group_project_id" icon="terp-folder-violet" domain="[]" context="{'group_by':'project_id'}"/>
<filter string="Stage" name="group_stage_id" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Last Stage Update" icon="terp-go-month" domain="[]" context="{'group_by':'date_last_stage_update'}"/>
<filter string="Deadline" icon="terp-gnome-cpu-frequency-applet+" domain="[]" context="{'group_by':'date_deadline'}"/>
<filter string="Start Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_start'}" groups="base.group_no_one"/>
<filter string="Start Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_start'}"/>
<filter string="End Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_end'}" groups="base.group_no_one"/>
</group>
</search>
@@ -89,17 +87,6 @@
<h1>
<field name="name" string="Project Name"/>
</h1>
<div name="group_alias"
attrs="{'invisible': [('alias_domain', '=', False)]}">
<label for="alias_id" string="Email Alias"/>
<field name="alias_id" class="oe_inline oe_read_only" required="0" nolabel="1"/>
<span name="edit_alias" class="oe_edit_only">
<field name="alias_name" class="oe_inline"
attrs="{'required': [('alias_id', '!=', False)]}"/>
@
<field name="alias_domain" class="oe_inline" readonly="1"/>
</span>
</div>
<div name="options_active">
<field name="use_tasks" class="oe_inline"/>
<label for="use_tasks"/>
@@ -112,17 +99,31 @@
</div>
<group>
<group>
<field name="privacy_visibility"/>
<field name="user_id" string="Project Manager"
attrs="{'readonly':[('state','in',['close', 'cancelled'])]}"
context="{'default_groups_ref': ['base.group_user', 'project.group_project_manager']}"/>
</group>
<group>
attrs="{'readonly':[('state','in',['close', 'cancelled'])]}"
context="{'default_groups_ref': ['base.group_user', 'project.group_project_manager']}"/>
<field name="partner_id" on_change="onchange_partner_id(partner_id)"/>
<p colspan="2" attrs="{'invisible': [('analytic_account_id','=',False)]}">
To invoice or setup invoicing and renewal options, go to the related contract: <field name="analytic_account_id" readonly="1" required="0" class="oe_inline"/>.
<span></span>
<p attrs="{'invisible': [('analytic_account_id','=',False)]}">
To invoice or setup invoicing and renewal options, go to the related contract:
<field name="analytic_account_id" readonly="1" required="0" class="oe_inline" nolabel="1"/>.
</p>
</group>
<group name="group_alias"
attrs="{'invisible': [('alias_domain', '=', False)]}">
<label for="alias_name" string="Email Alias"/>
<div name="alias_def">
<field name="alias_id" class="oe_read_only oe_inline"
string="Email Alias" required="0"/>
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
</div>
</div>
<label for="alias_model" string="Incoming Emails create"/>
<field name="alias_model" class="oe_inline" nolabel="1"/>
<field name="alias_contact" class="oe_inline"
string="Accept Emails From"/>
</group>
</group>
<notebook>
<page string="Team" name="team">
@@ -134,6 +135,7 @@
<div style="position: relative">
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
<div class="oe_module_vignette">
<img t-att-src="kanban_image('res.users', 'image_small', record.id.value)" class="oe_avatar oe_kanban_avatar_smallbox"/>
<div class="oe_module_desc">
<field name="name"/>
</div>
@@ -145,20 +147,22 @@
</field>
</page>
<page string="Other Info">
<group>
<group string="Administration" groups="project.group_time_work_estimation_tasks">
<field name="planned_hours" widget="float_time"/>
<field name="effective_hours" widget="float_time"/>
<field name="resource_calendar_id"/>
</group>
<group string="Miscellaneous" name="misc">
<field name="date_start"/>
<field name="date" string="End Date"/>
<field name="priority" groups="base.group_no_one"/>
<field name="active" attrs="{'invisible':[('state','in',['open', 'pending', 'template'])]}"/>
<field name="currency_id" groups="base.group_multi_currency" required="1"/>
<field name="parent_id" string="Parent" help="Append this project to another one using analytic accounts hierarchy" domain="[('id','!=',analytic_account_id)]" context="{'current_model': 'project.project'}" />
</group>
<group string="Administration">
<field name="privacy_visibility" widget="radio"/>
<field name="planned_hours" widget="float_time"
groups="project.group_time_work_estimation_tasks"/>
<field name="effective_hours" widget="float_time"
groups="project.group_time_work_estimation_tasks"/>
<field name="resource_calendar_id"
groups="project.group_time_work_estimation_tasks"/>
</group>
<group string="Miscellaneous" name="misc">
<field name="date_start"/>
<field name="date" string="End Date"/>
<field name="priority" groups="base.group_no_one"/>
<field name="active" attrs="{'invisible':[('state','in',['open', 'pending', 'template'])]}"/>
<field name="currency_id" groups="base.group_multi_currency" required="1"/>
<field name="parent_id" string="Parent" help="Append this project to another one using analytic accounts hierarchy" domain="[('id','!=',analytic_account_id)]" context="{'current_model': 'project.project'}" />
</group>
</page>
<page string="Project Stages" attrs="{'invisible': [('use_tasks', '=', False)]}" name="project_stages">
@@ -252,7 +256,7 @@
<div class="oe_kanban_content">
<h4><field name="name"/></h4>
<div class="oe_kanban_alias" t-if="record.alias_id.value">
<span class="oe_e">%%</span><small><field name="alias_id"/></small>
<span class="oe_e oe_e_alias">%%</span><small><field name="alias_id"/></small>
</div>
<div class="oe_kanban_project_list">
<a t-if="record.use_tasks.raw_value" name="%(act_project_project_2_project_task_all)d" type="action" style="margin-right: 10px">
@@ -365,18 +369,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">
@@ -390,8 +382,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"
@@ -399,15 +390,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"/>
@@ -421,7 +412,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>
@@ -430,7 +421,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"/>
@@ -439,7 +430,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"/>
@@ -447,12 +437,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>
@@ -461,6 +450,7 @@
<field name="date_end"/>
</group>
<group>
<field name="date_last_stage_update" groups="base.group_no_one"/>
</group>
</group>
</page>
@@ -487,7 +477,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"/>
@@ -507,7 +496,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/>
@@ -556,7 +544,7 @@
<field name="model">project.task</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<tree fonts="bold:message_unread==True" colors="grey:state in ('cancelled','done');blue:state == 'pending';red:date_deadline and (date_deadline&lt;current_date) and (state in ('draft','pending','open'))" string="Tasks">
<tree fonts="bold:message_unread==True" colors="red:date_deadline and (date_deadline&lt;current_date)" string="Tasks">
<field name="message_unread" invisible="1"/>
<field name="sequence" invisible="not context.get('seq_visible', False)"/>
<field name="name"/>
@@ -569,7 +557,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)"/>
@@ -655,7 +642,7 @@
<field name="res_model">project.task</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,graph,kanban</field>
<field name="domain">[('date_deadline','&lt;',time.strftime('%Y-%m-%d')),('state','in',('draft','pending','open'))]</field>
<field name="domain">[('date_deadline','&lt;',time.strftime('%Y-%m-%d'))]</field>
<field name="filter" eval="True"/>
<field name="search_view_id" ref="view_task_search_form"/>
</record>
@@ -700,7 +687,6 @@
<field name="case_default"/>
</group>
<group>
<field name="state"/>
<field name="sequence"/>
<field name="fold"/>
</group>
@@ -717,7 +703,7 @@
<tree string="Task Stage">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="state"/>
<field name="fold"/>
</tree>
</field>
</record>
@@ -761,7 +747,9 @@
</record>
<!-- User Form -->
<act_window context="{'search_default_user_id': [active_id], 'default_user_id': active_id}" domain="[('state', '&lt;&gt;', 'cancelled'),('state', '&lt;&gt;', 'done')]" id="act_res_users_2_project_task_opened" name="Assigned Tasks" res_model="project.task" src_model="res.users" view_mode="tree,form,gantt,calendar,graph" view_type="form"/>
<act_window context="{'search_default_user_id': [active_id], 'default_user_id': active_id}"
id="act_res_users_2_project_task_opened" name="Assigned Tasks"
res_model="project.task" src_model="res.users" view_mode="tree,form,gantt,calendar,graph" view_type="form"/>
<!-- Tags -->
<record model="ir.ui.view" id="project_category_search_view">
+1 -3
View File
@@ -14,7 +14,6 @@
<field name="user_id"/>
<field name="remaining_hours"/>
<field name="kanban_state"/>
<field name="state" groups="base.group_no_one"/>
</tree>
</field>
</record>
@@ -40,8 +39,7 @@
<field name="date"/>
<field name="project_id"/>
<field name="user_id"/>
<filter name="open" string="In Progress Tasks" domain="[('state','in',('open','draft'))]"/>
<filter string="Pending Tasks" domain="[('state','=','pending')]" context="{'show_delegated':False}"/>
<filter name="new" string="New" domain="[('type_id.sequence', '=', 1)]"/>
<separator/>
<filter name="kanban_blocked" string="Blocked" domain="[('kanban_state','=','blocked')]"/>
<filter name="kanban_ready" string="Ready" domain="[('kanban_state','=','done')]"/>
+13 -13
View File
@@ -19,9 +19,10 @@
#
##############################################################################
from openerp.osv import fields,osv
from openerp.osv import fields, osv
from openerp import tools
class report_project_task_user(osv.osv):
_name = "report.project.task.user"
_description = "Tasks by user and project"
@@ -31,10 +32,11 @@ class report_project_task_user(osv.osv):
'day': fields.char('Day', size=128, readonly=True),
'year': fields.char('Year', size=64, required=False, readonly=True),
'user_id': fields.many2one('res.users', 'Assigned To', readonly=True),
'date_start': fields.date('Starting Date',readonly=True),
'date_start': fields.date('Assignation Date', readonly=True),
'no_of_days': fields.integer('# of Days', size=128, readonly=True),
'date_end': fields.date('Ending Date', readonly=True),
'date_deadline': fields.date('Deadline', readonly=True),
'date_last_stage_update': fields.date('Last Stage Update', readonly=True),
'project_id': fields.many2one('project.project', 'Project', readonly=True),
'hours_planned': fields.float('Planned Hours', readonly=True),
'hours_effective': fields.float('Effective Hours', readonly=True),
@@ -44,16 +46,17 @@ class report_project_task_user(osv.osv):
'total_hours': fields.float('Total Hours', readonly=True),
'closing_days': fields.float('Days to Close', digits=(16,2), readonly=True, group_operator="avg",
help="Number of Days to close the task"),
'opening_days': fields.float('Days to Open', digits=(16,2), readonly=True, group_operator="avg",
'opening_days': fields.float('Days to Assign', digits=(16,2), readonly=True, group_operator="avg",
help="Number of Days to Open the task"),
'delay_endings_days': fields.float('Overpassed Deadline', digits=(16,2), readonly=True),
'nbr': fields.integer('# of tasks', readonly=True),
'priority' : fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Urgent'),
('0','Very urgent')], 'Priority', readonly=True),
'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')], 'Month', readonly=True),
'priority': fields.selection([('4', 'Very Low'), ('3', 'Low'), ('2', 'Medium'), ('1', 'Urgent'), ('0', 'Very urgent')],
string='Priority', readonly=True),
'month':fields.selection(fields.date.MONTHS, 'Month', readonly=True),
'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')],'Status', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'partner_id': fields.many2one('res.partner', 'Contact', readonly=True),
'stage_id': fields.many2one('project.task.type', 'Stage'),
}
_order = 'name desc, project_id'
@@ -69,19 +72,19 @@ class report_project_task_user(osv.osv):
to_char(date_start, 'YYYY-MM-DD') as day,
date_trunc('day',t.date_start) as date_start,
date_trunc('day',t.date_end) as date_end,
date_trunc('day',t.date_last_stage_update) as date_last_stage_update,
to_date(to_char(t.date_deadline, 'dd-MM-YYYY'),'dd-MM-YYYY') as date_deadline,
-- sum(cast(to_char(date_trunc('day',t.date_end) - date_trunc('day',t.date_start),'DD') as int)) as no_of_days,
abs((extract('epoch' from (t.date_end-t.date_start)))/(3600*24)) as no_of_days,
t.user_id,
progress as progress,
t.project_id,
t.state,
t.effective_hours as hours_effective,
t.priority,
t.name as name,
t.company_id,
t.partner_id,
t.stage_id,
t.stage_id as stage_id,
remaining_hours as remaining_hours,
total_hours as total_hours,
t.delay_hours as hours_delay,
@@ -106,15 +109,12 @@ class report_project_task_user(osv.osv):
date_start,
date_end,
date_deadline,
date_last_stage_update,
t.user_id,
t.project_id,
t.state,
t.priority,
name,
t.company_id,
t.partner_id,
t.stage_id
stage_id
""")
+18 -19
View File
@@ -13,11 +13,12 @@
<tree string="Tasks Analysis" create="false">
<field name="name" invisible="1"/>
<field name="project_id" invisible="1"/>
<field name="stage_id" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="date_deadline" invisible="1"/>
<field name="state" invisible="1"/>
<field name="date_start" invisible="1"/>
<field name="date_end" invisible="1"/>
<field name="date_last_stage_update" invisible="1"/>
<field name="company_id" invisible="1" groups="base.group_multi_company"/>
<field name="partner_id" invisible="1"/>
<field name="day" invisible="1"/>
@@ -44,7 +45,7 @@
<field name="arch" type="xml">
<graph string="Tasks Analysis" type="bar">
<field name="name"/>
<field name="state" group="True"/>
<field name="stage_id" group="True"/>
<field name="no_of_days" operator="+"/>
</graph>
</field>
@@ -58,31 +59,29 @@
<field name="date_start"/>
<field name="date_end"/>
<field name="date_deadline"/>
<filter string="New" icon="terp-document-new" domain="[('state','=','draft')]" help = "New tasks"/>
<filter string="In progress" icon="terp-check" domain="[('state', '=' ,'open')]" help = "In progress tasks"/>
<filter string="Pending" icon="terp-gtk-media-pause" domain="[('state','=','pending')]" help = "Pending tasks"/>
<filter string="Done" icon="terp-dialog-close" name="done" domain="[('state','=','done')]"/>
<separator/>
<filter icon="terp-folder-violet" string="My Projects" help="My Projects" domain="[('project_id.user_id','=',uid)]"/>
<separator/>
<filter icon="terp-personal" string="My Task" help = "My tasks" domain="[('user_id','=',uid)]" />
<filter icon="terp-personal-" string="Non Assigned Tasks to users" help="Non Assigned Tasks to users" domain="[('user_id','=',False)]"/>
<field name="date_last_stage_update"/>
<field name="project_id"/>
<field name="user_id"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="stage_id"/>
<filter string="Unassigned" name="unassigned" domain="[('user_id','=',False)]"/>
<filter string="New" name="new" domain="[('stage_id.sequence', '=', 1)]"/>
<separator/>
<filter string="My Task" domain="[('user_id','=',uid)]" />
<group expand="0" string="Extended Filters...">
<field name="priority"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group expand="1" string="Group By...">
<filter string="Project" name="project" icon="terp-folder-violet" context="{'group_by':'project_id'}"/>
<filter string="Task" icon="terp-stock_align_left_24" context="{'group_by':'name'}" />
<filter string="Contact" icon="terp-partner" context="{'group_by':'partner_id'}" />
<filter string="Assigned to" name="User" icon="terp-personal" context="{'group_by':'user_id'}" />
<filter string="Company" icon="terp-go-home" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Day" icon="terp-go-today" context="{'group_by':'day'}" help="Creation Date"/>
<filter string="Month" icon="terp-go-month" context="{'group_by':'month'}" help="Creation Date"/>
<filter string="Year" icon="terp-go-year" context="{'group_by':'year'}" help="Creation Date"/>
<filter string="Project" name="project" context="{'group_by':'project_id'}"/>
<filter string="Task" context="{'group_by':'name'}" />
<filter string="Contact" context="{'group_by':'partner_id'}" />
<filter string="Assigned to" name="User" context="{'group_by':'user_id'}" />
<filter string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Day" context="{'group_by':'day'}" help="Creation Date"/>
<filter string="Month" context="{'group_by':'month'}" help="Creation Date"/>
<filter string="Year" context="{'group_by':'year'}" help="Creation Date"/>
<filter string="Last Stage Update" context="{'group_by':'date_last_stage_update'}" help="Last Stage Update"/>
</group>
</search>
</field>
+8
View File
@@ -29,6 +29,14 @@ class res_partner(osv.osv):
'task_ids': fields.one2many('project.task', 'partner_id', 'Tasks'),
}
def copy(self, cr, uid, record_id, default=None, context=None):
if default is None:
default = {}
default['task_ids'] = []
return super(res_partner, self).copy(
cr, uid, record_id, default=default, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+9 -17
View File
@@ -4,28 +4,20 @@
<!-- Partners inherited form -->
<record id="view_task_partner_info_form" model="ir.ui.view">
<field name="name">res.partner.task.info.inherit</field>
<field name="name">res.partner.task.buttons</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="priority" eval="50"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='page_history']" position="attributes">
<attribute name="invisible">False</attribute>
</xpath>
<xpath expr="//page[@name='page_history']" position="inside">
<group name="grp_task" string="Tasks">
<field name="task_ids" colspan="4" nolabel="1">
<tree string="Tasks" editable="bottom">
<field name="name"/>
<field name="user_id"/>
<field name="date_deadline"/>
<field name="state" invisible="1"/>
<button name="do_open" states="pending,draft,done,cancelled" string="Start Task" type="object" icon="gtk-media-play" help="For changing to open state" invisible="context.get('set_visible',False)"/>
<button name="action_close" states="draft,pending,open" string="Done" type="object" icon="terp-dialog-close" help="For changing to done state"/>
</tree>
</field>
</group>
<xpath expr="//div[@name='buttons']" position="inside">
<button class="oe_inline" type="action"
name="%(action_view_task)d"
string="Tasks"
context="{'search_default_partner_id': active_id, 'default_partner_id': active_id}"
groups="project.group_project_user" />
</xpath>
</field>
</record>
</data>
</openerp>
@@ -19,6 +19,7 @@ access_project_task_history,project.task.history project,project.model_project_t
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_manager,1,0,0,0
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_user,1,0,0,0
access_resource_calendar,project.resource_calendar manager,resource.model_resource_calendar,project.group_project_manager,1,0,0,0
access_resource_calendar_leaves_user,resource.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1
access_project_category,project.project_category,model_project_category,,1,0,0,0
access_project_category_manager,project.project_category,model_project_category,project.group_project_manager,1,1,1,1
access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
19 access_project_task_history_cumulative project.task.history project project.model_project_task_history_cumulative project.group_project_manager 1 0 0 0
20 access_project_task_history_cumulative project.task.history project project.model_project_task_history_cumulative project.group_project_user 1 0 0 0
21 access_resource_calendar project.resource_calendar manager resource.model_resource_calendar project.group_project_manager 1 0 0 0
22 access_resource_calendar_leaves_user resource.calendar.leaves user resource.model_resource_calendar_leaves project.group_project_user 1 1 1 1
23 access_project_category project.project_category model_project_category 1 0 0 0
24 access_project_category_manager project.project_category model_project_category project.group_project_manager 1 1 1 1
25 access_mail_alias mail.alias mail.model_mail_alias project.group_project_manager 1 1 1 1

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -17,7 +17,7 @@ work done. Keep track of everything, from the big picture to the minute
details, from the customer contract to the billing.
</p>
<div class="oe_centeralign oe_websiteonly">
<a href="http://www.openerp.com/start" class="oe_button oe_big oe_tacky">Start your <span class="oe_emph">free</span> trial</a>
<a href="http://www.openerp.com/start?app=project" class="oe_button oe_big oe_tacky">Start your <span class="oe_emph">free</span> trial</a>
</div>
</div>
</div>

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

-12
View File
@@ -1,12 +0,0 @@
-
!record {model: project.project, id: project_project_1, view: False}:
partner_id: base.res_partner_2
-
!record {model: project.task, id: project_task_1, view: False}:
remaining_hours: 10.00
-
!record {model: project.task, id: project_task_1, view: False}:
planned_hours: 10.00
-
!record {model: project.task, id: project_task_1, view: False}:
project_id: project_project_1
-70
View File
@@ -1,70 +0,0 @@
-
In order to Test Process of Project Management,
-
I create duplicate template.
-
!python {model: project.project}: |
new_template = self.duplicate_template(cr, uid, [ref("project_project_1")])
assert new_template, "duplicate template is not created"
template = self.browse(cr, uid, new_template['res_id'], context=context)
assert template.state == 'open', "Duplicate template must be in open state."
-
I convert template into real Project.
-
!python {model: project.project}: |
self.reset_project(cr, uid, [ref("project_project_1")])
-
I check project details after convert from template.
-
!assert {model: project.project, id: project_project_1, severity: error, string: Project should be active}:
- state == "open"
-
I put project in pending.
-
!python {model: project.project}: |
self.set_pending(cr, uid, [ref("project_project_1")])
-
I check state after put in pending.
-
!assert {model: project.project, id: project_project_1, severity: error, string: Project should be in pending state}:
- state == "pending"
-
I re-open the project.
-
!python {model: project.project}: |
self.set_open(cr, uid, [ref("project_project_1")])
-
I check state after reopen.
-
!assert {model: project.project, id: project_project_1, severity: error, string: Project should be open.}:
- state == "open"
-
I close the project.
-
!python {model: project.project}: |
self.set_done(cr, uid, [ref("project_project_1")])
-
I check state after closed.
-
!assert {model: project.project, id: project_project_1, severity: error, string: Project should be close.}:
- state == "close"
-
I set project into template.
-
!python {model: project.project}: |
self.set_template(cr, uid, [ref("project_project_1")])
-
I schedule tasks of project.
-
!python {model: project.project}: |
self.schedule_tasks(cr, uid, [ref("project_project_1")], context=context)
-
I copy the tasks of project.
-
!python {model: project.project}: |
self.copy(cr, uid, ref("project_project_1"))
-
I cancel Project.
-
!python {model: project.project}: |
self.set_cancel(cr, uid, [ref("project_project_2")])
-76
View File
@@ -1,76 +0,0 @@
-
I put task in pending due to specification is not clear.
-
!python {model: project.task}: |
self.do_pending(cr, uid, [ref("project_task_1")])
context.update({"active_id": ref("project_task_1")})
-
I check state of task after put in pending.
-
!assert {model: project.task, id: project_task_1, severity: error, string: task should be in pending state}:
- state == "pending"
-
!record {model: project.task.delegate, id: delegate_id}:
user_id: base.user_demo
planned_hours: 12.0
planned_hours_me: 2.0
-
Now I delegate task to team member.
-
!python {model: project.task.delegate}: |
self.delegate(cr, uid, [ref("delegate_id")], {"active_id": ref("project_task_1")})
-
I check delegated task details.
-
!python {model: project.task}: |
task = self.browse(cr, uid, ref("project_task_1"), context=context)
assert task.planned_hours == 2.0, "Planning hours is not correct after delegated."
assert task.state == "pending", "Task should be in Pending after delegated."
-
I re-open the task.
-
!python {model: project.task}: |
self.do_reopen(cr, uid, [ref("project_task_1")])
-
I check reopened task details.
-
!assert {model: project.task, id: project_task_1, severity: error, string: task should be open.}:
- state == "open"
-
I change the stage of task to next stage.
-
!python {model: project.task}: |
self.stage_next(cr, uid, [ref("project_task_1")])
-
!record {model: project.task.reevaluate, id: reevaluate_id}:
remaining_hours : 120
-
I reevaluate task with remaining hours.
-
!python {model: project.task.reevaluate}: |
self.compute_hours(cr, uid, [ref("reevaluate_id")], {"active_id": ref("project_task_1")})
-
I check remaining hours after reevaluated task.
-
!assert {model: project.task, id: project_task_1, severity: error, string: task should be reevaluated}:
- remaining_hours == 120.0
-
I close the task.
-
!python {model: project.task}: |
self.action_close(cr, uid, [ref("project_task_1")])
-
I check state after closed.
-
!assert {model: project.task, id: project_task_1, severity: error, string: task is in open state}:
- state == "done"
-
I change the stage of task to previous stage.
-
!python {model: project.task}: |
self.stage_previous(cr, uid, [ref("project_task_1")])
-
I cancel Task.
-
!python {model: project.task}: |
self.do_cancel(cr, uid, [ref("project_task_2")])
+28
View File
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_project_flow
checks = [
test_project_flow,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+99
View File
@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.mail.tests.test_mail_base import TestMailBase
class TestProjectBase(TestMailBase):
def setUp(self):
super(TestProjectBase, self).setUp()
cr, uid = self.cr, self.uid
# Usefull models
self.project_project = self.registry('project.project')
self.project_task = self.registry('project.task')
self.project_task_delegate = self.registry('project.task.delegate')
# Find Project User group
group_project_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_user')
self.group_project_user_id = group_project_user_ref and group_project_user_ref[1] or False
# Find Project Manager group
group_project_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_manager')
self.group_project_manager_id = group_project_manager_ref and group_project_manager_ref[1] or False
# Test partners to use through the various tests
self.project_partner_id = self.res_partner.create(cr, uid, {
'name': 'Gertrude AgrolaitPartner',
'email': 'gertrude.partner@agrolait.com',
})
self.email_partner_id = self.res_partner.create(cr, uid, {
'name': 'Patrick Ratatouille',
'email': 'patrick.ratatouille@agrolait.com',
})
# Test users to use through the various tests
self.user_projectuser_id = self.res_users.create(cr, uid, {
'name': 'Armande ProjectUser',
'login': 'Armande',
'alias_name': 'armande',
'email': 'armande.projectuser@example.com',
'groups_id': [(6, 0, [self.group_employee_id, self.group_project_user_id])]
})
self.user_projectmanager_id = self.res_users.create(cr, uid, {
'name': 'Bastien ProjectManager',
'login': 'bastien',
'alias_name': 'bastien',
'email': 'bastien.projectmanager@example.com',
'groups_id': [(6, 0, [self.group_employee_id, self.group_project_manager_id])]
})
self.user_none_id = self.res_users.create(cr, uid, {
'name': 'Charlie Avotbonkeur',
'login': 'charlie',
'alias_name': 'charlie',
'email': 'charlie.noone@example.com',
'groups_id': [(6, 0, [])]
})
self.user_projectuser = self.res_users.browse(cr, uid, self.user_projectuser_id)
self.user_projectmanager = self.res_users.browse(cr, uid, self.user_projectmanager_id)
self.partner_projectuser_id = self.user_projectuser.partner_id.id
self.partner_projectmanager_id = self.user_projectmanager.partner_id.id
# Test 'Pigs' project
self.project_pigs_id = self.project_project.create(cr, uid, {
'name': 'Pigs',
'privacy_visibility': 'public',
'alias_name': 'project+pigs',
'partner_id': self.partner_raoul_id,
}, {'mail_create_nolog': True})
# Already-existing tasks in Pigs
self.task_1_id = self.project_task.create(cr, uid, {
'name': 'Pigs UserTask',
'user_id': self.user_projectuser_id,
'project_id': self.project_pigs_id,
}, {'mail_create_nolog': True})
self.task_2_id = self.project_task.create(cr, uid, {
'name': 'Pigs ManagerTask',
'user_id': self.user_projectmanager_id,
'project_id': self.project_pigs_id,
}, {'mail_create_nolog': True})
+163
View File
@@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.project.tests.test_project_base import TestProjectBase
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger
EMAIL_TPL = """Return-Path: <whatever-2a840@postmaster.twitter.com>
X-Original-To: {email_to}
Delivered-To: {email_to}
To: {email_to}
Received: by mail1.openerp.com (Postfix, from userid 10002)
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
Message-ID: {msg_id}
Date: Tue, 29 Nov 2011 12:43:21 +0530
From: {email_from}
MIME-Version: 1.0
Subject: {subject}
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Hello,
This email should create a new entry in your module. Please check that it
effectively works.
Thanks,
--
Raoul Boitempoils
Integrator at Agrolait"""
class TestProjectFlow(TestProjectBase):
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_00_project_process(self):
""" Testing project management """
cr, uid, user_projectuser_id, user_projectmanager_id, project_pigs_id = self.cr, self.uid, self.user_projectuser_id, self.user_projectmanager_id, self.project_pigs_id
# ProjectUser: set project as template -> raise
self.assertRaises(except_orm, self.project_project.set_template, cr, user_projectuser_id, [project_pigs_id])
# Other tests are done using a ProjectManager
project = self.project_project.browse(cr, user_projectmanager_id, project_pigs_id)
self.assertNotEqual(project.state, 'template', 'project: incorrect state, should not be a template')
# Set test project as template
self.project_project.set_template(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
self.assertEqual(project.state, 'template', 'project: set_template: project state should be template')
self.assertEqual(len(project.tasks), 0, 'project: set_template: project tasks should have been set inactive')
# Duplicate template
new_template_act = self.project_project.duplicate_template(cr, user_projectmanager_id, [project_pigs_id])
new_project = self.project_project.browse(cr, user_projectmanager_id, new_template_act['res_id'])
self.assertEqual(new_project.state, 'open', 'project: incorrect duplicate_template')
self.assertEqual(len(new_project.tasks), 2, 'project: duplicating a project template should duplicate its tasks')
# Convert into real project
self.project_project.reset_project(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
self.assertEqual(project.state, 'open', 'project: resetted project should be in open state')
self.assertEqual(len(project.tasks), 2, 'project: reset_project: project tasks should have been set active')
# Put as pending
self.project_project.set_pending(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
self.assertEqual(project.state, 'pending', 'project: should be in pending state')
# Re-open
self.project_project.set_open(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
self.assertEqual(project.state, 'open', 'project: reopened project should be in open state')
# Close project
self.project_project.set_done(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
self.assertEqual(project.state, 'close', 'project: closed project should be in close state')
# Re-open
self.project_project.set_open(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
# Re-convert into a template and schedule tasks
self.project_project.set_template(cr, user_projectmanager_id, [project_pigs_id])
self.project_project.schedule_tasks(cr, user_projectmanager_id, [project_pigs_id])
# Copy the project
new_project_id = self.project_project.copy(cr, user_projectmanager_id, project_pigs_id)
new_project = self.project_project.browse(cr, user_projectmanager_id, new_project_id)
self.assertEqual(len(new_project.tasks), 2, 'project: copied project should have copied task')
# Cancel the project
self.project_project.set_cancel(cr, user_projectmanager_id, [project_pigs_id])
self.assertEqual(project.state, 'cancelled', 'project: cancelled project should be in cancel state')
def test_10_task_process(self):
""" Testing task creation and management """
cr, uid, user_projectuser_id, user_projectmanager_id, project_pigs_id = self.cr, self.uid, self.user_projectuser_id, self.user_projectmanager_id, self.project_pigs_id
def format_and_process(template, email_to='project+pigs@mydomain.com, other@gmail.com', subject='Frogs',
email_from='Patrick Ratatouille <patrick.ratatouille@agrolait.com>',
msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'):
self.assertEqual(self.project_task.search(cr, uid, [('name', '=', subject)]), [])
mail = template.format(email_to=email_to, subject=subject, email_from=email_from, msg_id=msg_id)
self.mail_thread.message_process(cr, uid, None, mail)
return self.project_task.search(cr, uid, [('name', '=', subject)])
# Do: incoming mail from an unknown partner on an alias creates a new task 'Frogs'
frogs = format_and_process(EMAIL_TPL)
# Test: one task created by mailgateway administrator
self.assertEqual(len(frogs), 1, 'project: message_process: a new project.task should have been created')
task = self.project_task.browse(cr, user_projectuser_id, frogs[0])
res = self.project_task.perm_read(cr, uid, [task.id], details=False)
self.assertEqual(res[0].get('create_uid'), uid,
'project: message_process: task should have been created by uid as alias_user_id is False on the alias')
# Test: messages
self.assertEqual(len(task.message_ids), 3,
'project: message_process: newly created task should have 2 messages: creation and email')
self.assertEqual(task.message_ids[2].subtype_id.name, 'Task Created',
'project: message_process: first message of new task should have Task Created subtype')
self.assertEqual(task.message_ids[1].subtype_id.name, 'Task Assigned',
'project: message_process: first message of new task should have Task Created subtype')
self.assertEqual(task.message_ids[0].author_id.id, self.email_partner_id,
'project: message_process: second message should be the one from Agrolait (partner failed)')
self.assertEqual(task.message_ids[0].subject, 'Frogs',
'project: message_process: second message should be the one from Agrolait (subject failed)')
# Test: task content
self.assertEqual(task.name, 'Frogs', 'project_task: name should be the email subject')
self.assertEqual(task.project_id.id, self.project_pigs_id, 'project_task: incorrect project')
self.assertEqual(task.stage_id.sequence, 1, 'project_task: should have a stage with sequence=1')
# Open the delegation wizard
delegate_id = self.project_task_delegate.create(cr, user_projectuser_id, {
'user_id': user_projectuser_id,
'planned_hours': 12.0,
'planned_hours_me': 2.0,
}, {'active_id': task.id})
self.project_task_delegate.delegate(cr, user_projectuser_id, [delegate_id], {'active_id': task.id})
# Check delegation details
task.refresh()
self.assertEqual(task.planned_hours, 2, 'project_task_delegate: planned hours is not correct after delegation')
-2
View File
@@ -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>