2009-10-13 05:58:37 +00:00
# -*- coding: utf-8 -*-
2006-12-07 13:41:40 +00:00
##############################################################################
2010-01-08 11:05:05 +00:00
#
2009-10-14 11:15:34 +00:00
# OpenERP, Open Source Management Solution
2012-05-25 15:22:38 +00:00
# Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>)
2006-12-07 13:41:40 +00:00
#
2008-11-03 19:18:56 +00:00
# This program is free software: you can redistribute it and/or modify
2009-10-14 11:15:34 +00:00
# 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.
2006-12-07 13:41:40 +00:00
#
2008-11-03 19:18:56 +00:00
# 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
2009-10-14 11:15:34 +00:00
# GNU Affero General Public License for more details.
2006-12-07 13:41:40 +00:00
#
2009-10-14 11:15:34 +00:00
# You should have received a copy of the GNU Affero General Public License
2010-01-08 11:05:05 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2006-12-07 13:41:40 +00:00
#
##############################################################################
2010-07-09 07:27:50 +00:00
2012-10-09 15:54:20 +00:00
from datetime import datetime , date
2012-12-06 14:56:32 +00:00
from lxml import etree
import time
2012-10-09 15:54:20 +00:00
2012-08-31 13:51:36 +00:00
from openerp import SUPERUSER_ID
2012-12-06 15:13:16 +00:00
from openerp import tools
2013-07-10 12:25:26 +00:00
from openerp . addons . resource . faces import task as Task
2012-12-06 14:56:32 +00:00
from openerp . osv import fields , osv
2015-06-30 13:42:24 +00:00
from openerp . tools import float_is_zero
2012-12-06 14:56:32 +00:00
from openerp . tools . translate import _
2010-09-14 11:02:02 +00:00
2010-01-18 12:54:12 +00:00
class project_task_type ( osv . osv ) :
_name = ' project.task.type '
2010-01-31 21:29:06 +00:00
_description = ' Task Stage '
2010-08-05 07:20:23 +00:00
_order = ' sequence '
2010-01-18 12:54:12 +00:00
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Stage Name ' , required = True , translate = True ) ,
2010-01-18 12:54:12 +00:00
' description ' : fields . text ( ' Description ' ) ,
' sequence ' : fields . integer ( ' Sequence ' ) ,
2012-12-15 10:12:27 +00:00
' case_default ' : fields . boolean ( ' Default for New Projects ' ,
2012-05-25 15:22:38 +00:00
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. " ) ,
2011-04-07 11:41:45 +00:00
' project_ids ' : fields . many2many ( ' project.project ' , ' project_task_type_rel ' , ' type_id ' , ' project_id ' , ' Projects ' ) ,
2013-10-18 13:21:20 +00:00
' fold ' : fields . boolean ( ' Folded in Kanban View ' ,
help = ' This stage is folded in the kanban view when '
' there are no records in that stage to display. ' ) ,
2010-01-18 12:54:12 +00:00
}
2013-07-19 09:00:58 +00:00
2014-01-13 11:30:15 +00:00
def _get_default_project_ids ( self , cr , uid , ctx = { } ) :
project_id = self . pool [ ' project.task ' ] . _get_default_project_id ( cr , uid , context = ctx )
if project_id :
return [ project_id ]
return None
2010-01-18 12:54:12 +00:00
_defaults = {
2012-05-23 09:26:31 +00:00
' sequence ' : 1 ,
2014-01-13 11:30:15 +00:00
' project_ids ' : _get_default_project_ids ,
2010-01-18 12:54:12 +00:00
}
2011-10-27 18:23:07 +00:00
_order = ' sequence '
2010-01-18 13:06:09 +00:00
2006-12-07 13:41:40 +00:00
class project ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = " project.project "
_description = " Project "
2012-08-06 00:44:17 +00:00
_inherits = { ' account.analytic.account ' : " analytic_account_id " ,
" mail.alias " : " alias_id " }
2012-08-22 15:31:45 +00:00
_inherit = [ ' mail.thread ' , ' ir.needaction_mixin ' ]
2010-02-09 14:06:16 +00:00
2013-08-23 12:29:55 +00:00
def _auto_init ( self , cr , context = None ) :
""" Installation hook: aliases, project.project """
# create aliases for all projects and avoid constraint errors
alias_context = dict ( context , alias_model_name = ' project.task ' )
2014-01-06 13:36:00 +00:00
return self . pool . get ( ' mail.alias ' ) . migrate_to_alias ( cr , self . _name , self . _table , super ( project , self ) . _auto_init ,
2013-08-23 16:18:10 +00:00
' project.task ' , self . _columns [ ' alias_id ' ] , ' id ' , alias_prefix = ' project+ ' , alias_defaults = { ' project_id ' : ' id ' } , context = alias_context )
2013-08-23 12:29:55 +00:00
2010-03-16 12:51:46 +00:00
def search ( self , cr , user , args , offset = 0 , limit = None , order = None , context = None , count = False ) :
2010-02-11 06:35:49 +00:00
if user == 1 :
2010-07-09 07:27:50 +00:00
return super ( project , self ) . search ( cr , user , args , offset = offset , limit = limit , order = order , context = context , count = count )
2011-09-06 07:37:42 +00:00
if context and context . get ( ' user_preference ' ) :
2010-02-09 14:06:16 +00:00
cr . execute ( """ SELECT project.id FROM project_project project
2010-07-15 07:35:17 +00:00
LEFT JOIN account_analytic_account account ON account . id = project . analytic_account_id
2013-05-31 07:36:31 +00:00
LEFT JOIN project_user_rel rel ON rel . project_id = project . id
2010-02-11 06:35:49 +00:00
WHERE ( account . user_id = % s or rel . uid = % s ) """ % (user, user))
2010-08-05 07:20:23 +00:00
return [ ( r [ 0 ] ) for r in cr . fetchall ( ) ]
2010-02-09 14:06:16 +00:00
return super ( project , self ) . search ( cr , user , args , offset = offset , limit = limit , order = order ,
context = context , count = count )
2010-07-02 14:37:06 +00:00
def onchange_partner_id ( self , cr , uid , ids , part = False , context = None ) :
2010-03-16 12:51:46 +00:00
partner_obj = self . pool . get ( ' res.partner ' )
2012-03-07 07:02:25 +00:00
val = { }
2013-07-19 09:00:58 +00:00
if not part :
return { ' value ' : val }
2011-11-17 08:28:10 +00:00
if ' pricelist_id ' in self . fields_get ( cr , uid , context = context ) :
2011-11-14 21:33:19 +00:00
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
val [ ' pricelist_id ' ] = pricelist_id
return { ' value ' : val }
2006-12-07 13:41:40 +00:00
2012-02-06 10:36:55 +00:00
def _get_projects_from_tasks ( self , cr , uid , task_ids , context = None ) :
tasks = self . pool . get ( ' project.task ' ) . browse ( cr , uid , task_ids , context = context )
2012-02-07 11:32:34 +00:00
project_ids = [ task . project_id . id for task in tasks if task . project_id ]
2012-02-06 10:36:55 +00:00
return self . pool . get ( ' project.project ' ) . _get_project_and_parents ( cr , uid , project_ids , context )
def _get_project_and_parents ( self , cr , uid , ids , context = None ) :
""" return the project ids and all their parent projects """
res = set ( ids )
while ids :
cr . execute ( """
SELECT DISTINCT parent . id
FROM project_project project , project_project parent , account_analytic_account account
WHERE project . analytic_account_id = account . id
AND parent . analytic_account_id = account . parent_id
AND project . id IN % s
""" , (tuple(ids),))
2012-02-07 09:26:23 +00:00
ids = [ t [ 0 ] for t in cr . fetchall ( ) ]
2012-02-06 10:36:55 +00:00
res . update ( ids )
return list ( res )
def _get_project_and_children ( self , cr , uid , ids , context = None ) :
2012-02-06 11:29:40 +00:00
""" retrieve all children projects of project ids;
return a dictionary mapping each project to its parent project ( or None )
"""
res = dict . fromkeys ( ids , None )
2012-02-06 10:36:55 +00:00
while ids :
cr . execute ( """
2012-02-06 11:29:40 +00:00
SELECT project . id , parent . id
2012-02-06 10:36:55 +00:00
FROM project_project project , project_project parent , account_analytic_account account
WHERE project . analytic_account_id = account . id
AND parent . analytic_account_id = account . parent_id
AND parent . id IN % s
""" , (tuple(ids),))
2012-02-06 11:29:40 +00:00
dic = dict ( cr . fetchall ( ) )
res . update ( dic )
ids = dic . keys ( )
2008-08-27 13:48:40 +00:00
return res
2010-07-02 14:37:06 +00:00
def _progress_rate ( self , cr , uid , ids , names , arg , context = None ) :
2012-02-06 11:29:40 +00:00
child_parent = self . _get_project_and_children ( cr , uid , ids , context )
# compute planned_hours, total_hours, effective_hours specific to each project
cr . execute ( """
SELECT project_id , COALESCE ( SUM ( planned_hours ) , 0.0 ) ,
COALESCE ( SUM ( total_hours ) , 0.0 ) , COALESCE ( SUM ( effective_hours ) , 0.0 )
2013-06-26 13:25:19 +00:00
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
2012-02-06 11:29:40 +00:00
GROUP BY project_id
""" , (tuple(child_parent.keys()),))
# aggregate results into res
2013-06-26 13:25:19 +00:00
res = dict ( [ ( id , { ' planned_hours ' : 0.0 , ' total_hours ' : 0.0 , ' effective_hours ' : 0.0 } ) for id in ids ] )
2012-02-06 11:29:40 +00:00
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 :
if id in ids :
res [ id ] [ ' planned_hours ' ] + = planned
res [ id ] [ ' total_hours ' ] + = total
res [ id ] [ ' effective_hours ' ] + = effective
id = child_parent [ id ]
# compute progress rates
2012-02-06 10:36:55 +00:00
for id in ids :
2012-02-06 11:29:40 +00:00
if res [ id ] [ ' total_hours ' ] :
res [ id ] [ ' progress_rate ' ] = round ( 100.0 * res [ id ] [ ' effective_hours ' ] / res [ id ] [ ' total_hours ' ] , 2 )
2012-02-06 10:36:55 +00:00
else :
2012-02-06 13:06:31 +00:00
res [ id ] [ ' progress_rate ' ] = 0.0
2008-08-27 13:48:40 +00:00
return res
2010-10-12 11:43:30 +00:00
2012-11-01 09:07:56 +00:00
def unlink ( self , cr , uid , ids , context = None ) :
2012-07-05 12:54:17 +00:00
alias_ids = [ ]
2012-07-05 14:17:06 +00:00
mail_alias = self . pool . get ( ' mail.alias ' )
2012-11-01 09:07:56 +00:00
for proj in self . browse ( cr , uid , ids , context = context ) :
2008-10-27 23:58:25 +00:00
if proj . tasks :
2012-08-07 12:04:51 +00:00
raise osv . except_osv ( _ ( ' Invalid Action! ' ) ,
_ ( ' 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. ' ) )
2012-07-05 12:54:17 +00:00
elif proj . alias_id :
alias_ids . append ( proj . alias_id . id )
2013-04-10 16:01:39 +00:00
res = super ( project , self ) . unlink ( cr , uid , ids , context = context )
2012-11-01 09:07:56 +00:00
mail_alias . unlink ( cr , uid , alias_ids , context = context )
2012-07-05 12:54:17 +00:00
return res
2013-04-10 16:01:39 +00:00
2012-10-30 13:32:22 +00:00
def _get_attached_docs ( self , cr , uid , ids , field_name , arg , context ) :
res = { }
attachment = self . pool . get ( ' ir.attachment ' )
task = self . pool . get ( ' project.task ' )
for id in ids :
2012-12-15 10:12:27 +00:00
project_attachments = attachment . search ( cr , uid , [ ( ' res_model ' , ' = ' , ' project.project ' ) , ( ' res_id ' , ' = ' , id ) ] , context = context , count = True )
task_ids = task . search ( cr , uid , [ ( ' project_id ' , ' = ' , id ) ] , context = context )
2012-11-02 08:08:36 +00:00
task_attachments = attachment . search ( cr , uid , [ ( ' res_model ' , ' = ' , ' project.task ' ) , ( ' res_id ' , ' in ' , task_ids ) ] , context = context , count = True )
2012-12-15 10:12:27 +00:00
res [ id ] = ( project_attachments or 0 ) + ( task_attachments or 0 )
2012-10-30 13:32:22 +00:00
return res
2012-05-07 07:20:49 +00:00
def _task_count ( self , cr , uid , ids , field_name , arg , context = None ) :
2014-03-20 09:46:14 +00:00
res = { }
2015-02-23 15:05:41 +00:00
for tasks in self . browse ( cr , uid , ids , dict ( context , active_test = False ) ) :
2014-03-20 09:46:14 +00:00
res [ tasks . id ] = len ( tasks . task_ids )
2012-05-07 10:07:42 +00:00
return res
2012-08-01 09:14:39 +00:00
def _get_alias_models ( self , cr , uid , context = None ) :
2013-04-10 16:01:39 +00:00
""" Overriden in project_issue to offer more options """
2012-07-04 09:33:43 +00:00
return [ ( ' project.task ' , " Tasks " ) ]
2012-12-15 10:12:27 +00:00
2013-04-10 16:01:39 +00:00
def _get_visibility_selection ( self , cr , uid , context = None ) :
2013-04-12 14:37:19 +00:00
""" Overriden in portal_project to offer more options """
2014-11-02 12:25:15 +00:00
return [ ( ' public ' , _ ( ' Public project ' ) ) ,
( ' employees ' , _ ( ' Internal project: all employees can access ' ) ) ,
( ' followers ' , _ ( ' Private project: followers Only ' ) ) ]
2013-04-10 16:01:39 +00:00
2012-10-30 07:26:56 +00:00
def attachment_tree_view ( self , cr , uid , ids , context ) :
task_ids = self . pool . get ( ' project.task ' ) . search ( cr , uid , [ ( ' project_id ' , ' in ' , ids ) ] )
2012-11-02 08:08:36 +00:00
domain = [
2013-04-10 16:01:39 +00:00
' | ' ,
2012-11-09 08:39:03 +00:00
' & ' , ( ' res_model ' , ' = ' , ' project.project ' ) , ( ' res_id ' , ' in ' , ids ) ,
2013-04-10 16:01:39 +00:00
' & ' , ( ' res_model ' , ' = ' , ' project.task ' ) , ( ' res_id ' , ' in ' , task_ids ) ]
2012-11-01 11:57:57 +00:00
res_id = ids and ids [ 0 ] or False
2012-10-30 07:26:56 +00:00
return {
2012-11-02 08:08:36 +00:00
' name ' : _ ( ' Attachments ' ) ,
' domain ' : domain ,
' res_model ' : ' ir.attachment ' ,
' type ' : ' ir.actions.act_window ' ,
' view_id ' : False ,
2014-05-13 11:18:37 +00:00
' view_mode ' : ' kanban,tree,form ' ,
2012-11-02 08:08:36 +00:00
' view_type ' : ' form ' ,
' limit ' : 80 ,
' context ' : " { ' default_res_model ' : ' %s ' , ' default_res_id ' : %d } " % ( self . _name , res_id )
}
2013-04-10 16:01:39 +00:00
2012-08-07 12:04:51 +00:00
# Lambda indirection method to avoid passing a copy of the overridable method when declaring the field
2012-08-01 09:14:39 +00:00
_alias_models = lambda self , * args , * * kwargs : self . _get_alias_models ( * args , * * kwargs )
2013-04-10 16:01:39 +00:00
_visibility_selection = lambda self , * args , * * kwargs : self . _get_visibility_selection ( * args , * * kwargs )
2008-07-22 15:11:28 +00:00
_columns = {
2010-11-15 11:12:22 +00:00
' active ' : fields . boolean ( ' Active ' , help = " If the active field is set to False, it will allow you to hide the project without removing it. " ) ,
2010-02-11 05:34:49 +00:00
' sequence ' : fields . integer ( ' Sequence ' , help = " Gives the sequence order when displaying a list of Projects. " ) ,
2014-08-07 10:50:13 +00:00
' analytic_account_id ' : fields . many2one (
' account.analytic.account ' , ' Contract/Analytic ' ,
help = " Link this project to an analytic account if you need financial management on projects. "
" It enables you to connect projects with budgets, planning, cost and revenue analysis, timesheets on projects, etc. " ,
ondelete = " cascade " , required = True , auto_join = True ) ,
2011-01-17 07:41:14 +00:00
' members ' : fields . many2many ( ' res.users ' , ' project_user_rel ' , ' project_id ' , ' uid ' , ' Project Members ' ,
help = " Project ' s members are users who can have an access to the tasks related to this project. " , states = { ' close ' : [ ( ' readonly ' , True ) ] , ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
2012-04-30 09:11:38 +00:00
' tasks ' : fields . one2many ( ' project.task ' , ' project_id ' , " Task Activities " ) ,
2011-07-01 23:41:24 +00:00
' 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. " ,
2010-10-12 11:43:30 +00:00
store = {
2012-02-06 10:36:55 +00:00
' project.project ' : ( _get_project_and_parents , [ ' tasks ' , ' parent_id ' , ' child_ids ' ] , 10 ) ,
2013-06-26 13:25:19 +00:00
' project.task ' : ( _get_projects_from_tasks , [ ' planned_hours ' , ' remaining_hours ' , ' work_ids ' , ' stage_id ' ] , 20 ) ,
2012-02-06 13:08:04 +00:00
} ) ,
' 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 ) ,
2013-06-26 13:25:19 +00:00
' project.task ' : ( _get_projects_from_tasks , [ ' planned_hours ' , ' remaining_hours ' , ' work_ids ' , ' stage_id ' ] , 20 ) ,
2010-10-12 11:43:30 +00:00
} ) ,
2011-07-01 23:41:24 +00:00
' 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. " ,
2010-10-12 11:43:30 +00:00
store = {
2012-02-06 13:08:04 +00:00
' project.project ' : ( _get_project_and_parents , [ ' tasks ' , ' parent_id ' , ' child_ids ' ] , 10 ) ,
2013-06-26 13:25:19 +00:00
' project.task ' : ( _get_projects_from_tasks , [ ' planned_hours ' , ' remaining_hours ' , ' work_ids ' , ' stage_id ' ] , 20 ) ,
2010-10-12 11:43:30 +00:00
} ) ,
2012-02-06 13:08:04 +00:00
' 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 ) ,
2013-06-26 13:25:19 +00:00
' project.task ' : ( _get_projects_from_tasks , [ ' planned_hours ' , ' remaining_hours ' , ' work_ids ' , ' stage_id ' ] , 20 ) ,
2010-10-12 11:43:30 +00:00
} ) ,
2012-02-06 13:08:04 +00:00
' resource_calendar_id ' : fields . many2one ( ' resource.calendar ' , ' Working Time ' , help = " Timetable working hours to adjust the gantt diagram report " , states = { ' close ' : [ ( ' readonly ' , True ) ] } ) ,
2010-07-29 05:31:04 +00:00
' type_ids ' : fields . many2many ( ' project.task.type ' , ' project_task_type_rel ' , ' project_id ' , ' type_id ' , ' Tasks Stages ' , states = { ' close ' : [ ( ' readonly ' , True ) ] , ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
2014-03-20 11:20:17 +00:00
' task_count ' : fields . function ( _task_count , type = ' integer ' , string = " Tasks " , ) ,
2013-10-18 13:21:20 +00:00
' task_ids ' : fields . one2many ( ' project.task ' , ' project_id ' ,
2013-10-23 11:37:16 +00:00
domain = [ ( ' stage_id.fold ' , ' = ' , False ) ] ) ,
2012-04-09 10:58:30 +00:00
' color ' : fields . integer ( ' Color Index ' ) ,
2013-08-27 15:07:08 +00:00
' alias_id ' : fields . many2one ( ' mail.alias ' , ' Alias ' , ondelete = " restrict " , required = True ,
2012-08-06 00:44:17 +00:00
help = " Internal email associated with this project. Incoming emails are automatically synchronized "
" with Tasks (or optionally Issues if the Issue Tracker module is installed). " ) ,
2012-08-07 12:04:51 +00:00
' alias_model ' : fields . selection ( _alias_models , " Alias Model " , select = True , required = True ,
2012-08-06 00:44:17 +00:00
help = " The kind of document created when an email is received on this project ' s email alias " ) ,
2013-04-26 13:02:59 +00:00
' privacy_visibility ' : fields . selection ( _visibility_selection , ' Privacy / Visibility ' , required = True ,
help = " Holds visibility of the tasks or issues that belong to the current project: \n "
" - Public: everybody sees everything; if portal is activated, portal users \n "
" see all tasks or issues; if anonymous portal is activated, visitors \n "
" see all tasks or issues \n "
" - Portal (only available if Portal is installed): employees see everything; \n "
" if portal is activated, portal users see the tasks or issues followed by \n "
" them or by someone of their company \n "
" - Employees Only: employees see all tasks or issues \n "
" - Followers Only: employees see only the followed tasks or issues; if portal \n "
" is activated, portal users see the followed tasks or issues. " ) ,
2014-07-06 14:44:26 +00:00
' state ' : fields . selection ( [ ( ' template ' , ' Template ' ) ,
( ' draft ' , ' New ' ) ,
( ' open ' , ' In Progress ' ) ,
( ' cancelled ' , ' Cancelled ' ) ,
( ' pending ' , ' Pending ' ) ,
( ' close ' , ' Closed ' ) ] ,
' Status ' , required = True , copy = False ) ,
2014-03-26 22:33:29 +00:00
' doc_count ' : fields . function (
_get_attached_docs , string = " Number of documents attached " , type = ' integer '
)
2008-07-22 15:11:28 +00:00
}
2012-07-11 13:21:30 +00:00
2011-08-15 14:21:41 +00:00
def _get_type_common ( self , cr , uid , context ) :
2012-05-25 15:22:38 +00:00
ids = self . pool . get ( ' project.task.type ' ) . search ( cr , uid , [ ( ' case_default ' , ' = ' , 1 ) ] , context = context )
2011-08-15 14:21:41 +00:00
return ids
2012-12-13 15:14:18 +00:00
_order = " sequence, id "
2008-07-22 15:11:28 +00:00
_defaults = {
2010-07-02 14:37:06 +00:00
' active ' : True ,
2012-06-15 08:12:54 +00:00
' type ' : ' contract ' ,
2012-06-08 09:05:37 +00:00
' state ' : ' open ' ,
2010-07-02 14:37:06 +00:00
' sequence ' : 10 ,
2012-03-28 09:37:42 +00:00
' type_ids ' : _get_type_common ,
2012-08-01 09:14:39 +00:00
' alias_model ' : ' project.task ' ,
2013-04-16 13:39:36 +00:00
' privacy_visibility ' : ' employees ' ,
2008-07-22 15:11:28 +00:00
}
2010-07-27 12:43:02 +00:00
2010-10-17 20:31:19 +00:00
# TODO: Why not using a SQL contraints ?
2010-11-22 10:37:53 +00:00
def _check_dates ( self , cr , uid , ids , context = None ) :
for leave in self . read ( cr , uid , ids , [ ' date_start ' , ' date ' ] , context = context ) :
2011-04-29 08:49:48 +00:00
if leave [ ' date_start ' ] and leave [ ' date ' ] :
if leave [ ' date_start ' ] > leave [ ' date ' ] :
return False
2010-11-22 10:37:53 +00:00
return True
2010-06-08 12:34:12 +00:00
2010-01-18 13:44:53 +00:00
_constraints = [
2015-03-06 16:06:12 +00:00
( _check_dates , ' Error! project start-date must be lower than project end-date. ' , [ ' date_start ' , ' date ' ] )
2010-01-18 13:44:53 +00:00
]
2010-07-27 12:43:02 +00:00
2010-07-02 14:37:06 +00:00
def set_template ( self , cr , uid , ids , context = None ) :
2013-07-10 12:25:26 +00:00
return self . setActive ( cr , uid , ids , value = False , context = context )
2008-09-23 06:14:43 +00:00
2010-07-02 14:37:06 +00:00
def set_done ( self , cr , uid , ids , context = None ) :
2013-06-26 13:25:19 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' close ' } , context = context )
2008-10-27 11:13:26 +00:00
2010-07-02 14:37:06 +00:00
def set_cancel ( self , cr , uid , ids , context = None ) :
2013-06-26 13:25:19 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' cancelled ' } , context = context )
2008-10-27 11:13:26 +00:00
2010-07-02 14:37:06 +00:00
def set_pending ( self , cr , uid , ids , context = None ) :
2013-06-26 13:25:19 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' pending ' } , context = context )
2008-10-27 11:13:26 +00:00
2010-07-02 14:37:06 +00:00
def set_open ( self , cr , uid , ids , context = None ) :
2013-06-26 13:25:19 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' open ' } , context = context )
2008-10-27 11:13:26 +00:00
2010-07-02 14:37:06 +00:00
def reset_project ( self , cr , uid , ids , context = None ) :
2012-12-18 15:36:01 +00:00
return self . setActive ( cr , uid , ids , value = True , context = context )
2012-03-15 13:19:19 +00:00
2012-02-15 14:51:12 +00:00
def map_tasks ( self , cr , uid , old_project_id , new_project_id , context = None ) :
""" copy and map tasks from old to new project """
if context is None :
context = { }
2011-12-21 12:30:36 +00:00
map_task_id = { }
2012-02-15 14:51:12 +00:00
task_obj = self . pool . get ( ' project.task ' )
proj = self . browse ( cr , uid , old_project_id , context = context )
2011-12-21 12:30:36 +00:00
for task in proj . tasks :
2014-07-06 14:44:26 +00:00
# preserve task name and stage, normally altered during copy
defaults = { ' stage_id ' : task . stage_id . id ,
' name ' : task . name }
map_task_id [ task . id ] = task_obj . copy ( cr , uid , task . id , defaults , context = context )
2012-04-03 10:05:56 +00:00
self . write ( cr , uid , [ new_project_id ] , { ' tasks ' : [ ( 6 , 0 , map_task_id . values ( ) ) ] } )
2011-12-21 12:30:36 +00:00
task_obj . duplicate_task ( cr , uid , map_task_id , context = context )
return True
2008-09-23 06:14:43 +00:00
2012-03-05 18:40:03 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
2012-10-02 11:12:31 +00:00
if default is None :
default = { }
2014-07-06 14:44:26 +00:00
context = dict ( context or { } )
2009-05-05 21:14:47 +00:00
context [ ' active_test ' ] = False
2011-04-05 06:02:10 +00:00
proj = self . browse ( cr , uid , id , context = context )
2014-07-06 14:44:26 +00:00
if not default . get ( ' name ' ) :
2012-09-24 16:26:45 +00:00
default . update ( name = _ ( " %s (copy) " ) % ( proj . name ) )
2009-05-05 21:14:47 +00:00
res = super ( project , self ) . copy ( cr , uid , id , default , context )
2013-04-09 11:11:58 +00:00
self . map_tasks ( cr , uid , id , res , context = context )
2011-03-02 21:43:56 +00:00
return res
2010-07-02 14:37:06 +00:00
def duplicate_template ( self , cr , uid , ids , context = None ) :
2014-07-06 14:44:26 +00:00
context = dict ( context or { } )
2010-04-05 10:17:04 +00:00
data_obj = self . pool . get ( ' ir.model.data ' )
2010-01-22 07:41:48 +00:00
result = [ ]
2010-03-16 12:51:46 +00:00
for proj in self . browse ( cr , uid , ids , context = context ) :
2010-08-12 07:31:45 +00:00
parent_id = context . get ( ' parent_id ' , False )
2010-07-01 09:08:44 +00:00
context . update ( { ' analytic_project_copy ' : True } )
2010-08-12 07:31:45 +00:00
new_date_start = time . strftime ( ' % Y- % m- %d ' )
new_date_end = False
if proj . date_start and proj . date :
start_date = date ( * time . strptime ( proj . date_start , ' % Y- % m- %d ' ) [ : 3 ] )
end_date = date ( * time . strptime ( proj . date , ' % Y- % m- %d ' ) [ : 3 ] )
new_date_end = ( datetime ( * time . strptime ( new_date_start , ' % Y- % m- %d ' ) [ : 3 ] ) + ( end_date - start_date ) ) . strftime ( ' % Y- % m- %d ' )
2011-02-18 07:36:11 +00:00
context . update ( { ' copy ' : True } )
2012-02-15 14:20:23 +00:00
new_id = self . copy ( cr , uid , proj . id , default = {
2012-09-24 16:26:45 +00:00
' name ' : _ ( " %s (copy) " ) % ( proj . name ) ,
2010-01-22 07:41:48 +00:00
' state ' : ' open ' ,
2010-08-12 07:31:45 +00:00
' date_start ' : new_date_start ,
' date ' : new_date_end ,
2010-03-16 12:51:46 +00:00
' parent_id ' : parent_id } , context = context )
2010-01-22 07:41:48 +00:00
result . append ( new_id )
2010-09-27 13:44:03 +00:00
2010-07-15 07:35:17 +00:00
child_ids = self . search ( cr , uid , [ ( ' parent_id ' , ' = ' , proj . analytic_account_id . id ) ] , context = context )
parent_id = self . read ( cr , uid , new_id , [ ' analytic_account_id ' ] ) [ ' analytic_account_id ' ] [ 0 ]
2010-01-22 06:25:43 +00:00
if child_ids :
2010-07-01 05:50:01 +00:00
self . duplicate_template ( cr , uid , child_ids , context = { ' parent_id ' : parent_id } )
2010-04-05 10:17:04 +00:00
if result and len ( result ) :
res_id = result [ 0 ]
form_view_id = data_obj . _get_id ( cr , uid , ' project ' , ' edit_project ' )
form_view = data_obj . read ( cr , uid , form_view_id , [ ' res_id ' ] )
2010-08-13 05:07:55 +00:00
tree_view_id = data_obj . _get_id ( cr , uid , ' project ' , ' view_project ' )
2010-04-05 10:17:04 +00:00
tree_view = data_obj . read ( cr , uid , tree_view_id , [ ' res_id ' ] )
search_view_id = data_obj . _get_id ( cr , uid , ' project ' , ' view_project_project_filter ' )
search_view = data_obj . read ( cr , uid , search_view_id , [ ' res_id ' ] )
return {
' name ' : _ ( ' Projects ' ) ,
' view_type ' : ' form ' ,
' view_mode ' : ' form,tree ' ,
' res_model ' : ' project.project ' ,
' view_id ' : False ,
2010-07-02 14:37:06 +00:00
' res_id ' : res_id ,
2010-04-05 10:17:04 +00:00
' views ' : [ ( form_view [ ' res_id ' ] , ' form ' ) , ( tree_view [ ' res_id ' ] , ' tree ' ) ] ,
' type ' : ' ir.actions.act_window ' ,
' search_view_id ' : search_view [ ' res_id ' ] ,
' nodestroy ' : True
2010-08-13 12:20:05 +00:00
}
2006-12-07 13:41:40 +00:00
2008-07-22 15:11:28 +00:00
# set active value for a project, its sub projects and its tasks
2010-07-02 14:37:06 +00:00
def setActive ( self , cr , uid , ids , value = True , context = None ) :
2010-03-16 12:51:46 +00:00
task_obj = self . pool . get ( ' project.task ' )
2010-07-02 14:37:06 +00:00
for proj in self . browse ( cr , uid , ids , context = None ) :
2008-09-12 05:42:31 +00:00
self . write ( cr , uid , [ proj . id ] , { ' state ' : value and ' open ' or ' template ' } , context )
2008-12-10 14:29:55 +00:00
cr . execute ( ' select id from project_task where project_id= %s ' , ( proj . id , ) )
2008-09-12 10:25:46 +00:00
tasks_id = [ x [ 0 ] for x in cr . fetchall ( ) ]
2008-09-12 05:42:31 +00:00
if tasks_id :
2010-03-16 12:51:46 +00:00
task_obj . write ( cr , uid , tasks_id , { ' active ' : value } , context = context )
2010-07-20 05:58:59 +00:00
child_ids = self . search ( cr , uid , [ ( ' parent_id ' , ' = ' , proj . analytic_account_id . id ) ] )
2010-01-22 06:25:43 +00:00
if child_ids :
2010-07-02 14:37:06 +00:00
self . setActive ( cr , uid , child_ids , value , context = None )
2008-07-22 15:11:28 +00:00
return True
2010-10-11 05:51:53 +00:00
2011-11-13 15:40:52 +00:00
def _schedule_header ( self , cr , uid , ids , force_members = True , context = None ) :
context = context or { }
if type ( ids ) in ( long , int , ) :
ids = [ ids ]
projects = self . browse ( cr , uid , ids , context = context )
for project in projects :
if ( not project . members ) and force_members :
2013-04-29 07:15:57 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( " You must assign members on the project ' %s ' ! " ) % ( project . name , ) )
2011-11-13 15:40:52 +00:00
resource_pool = self . pool . get ( ' resource.resource ' )
2012-01-09 13:12:05 +00:00
result = " from openerp.addons.resource.faces import * \n "
2011-11-13 15:40:52 +00:00
result + = " import datetime \n "
for project in self . browse ( cr , uid , ids , context = context ) :
u_ids = [ i . id for i in project . members ]
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 . 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
resource_objs = resource_pool . generate_resources ( cr , uid , u_ids , calendar_id , context = context )
for key , vals in resource_objs . items ( ) :
result + = '''
class User_ % s ( Resource ) :
efficiency = % s
''' % (key, vals.get( ' efficiency ' , False))
result + = '''
def Project ( ) :
'''
return result
def _schedule_project ( self , cr , uid , project , context = None ) :
resource_pool = self . pool . get ( ' resource.resource ' )
calendar_id = project . resource_calendar_id and project . resource_calendar_id . id or False
working_days = resource_pool . compute_working_calendar ( cr , uid , calendar_id , context = context )
# TODO: check if we need working_..., default values are ok.
puids = [ x . id for x in project . members ]
if project . user_id :
puids . append ( project . user_id . id )
result = """
def Project_ % d ( ) :
start = \' %s \'
working_days = % s
resource = % s
""" % (
2011-12-09 07:30:04 +00:00
project . id ,
2012-11-13 07:54:26 +00:00
project . date_start or time . strftime ( ' % Y- % m- %d ' ) , working_days ,
2013-12-19 15:45:46 +00:00
' | ' . join ( [ ' User_ ' + str ( x ) for x in puids ] ) or ' None '
2011-11-13 15:40:52 +00:00
)
vacation = calendar_id and tuple ( resource_pool . compute_vacation ( cr , uid , calendar_id , context = context ) ) or False
if vacation :
result + = """
vacation = % s
""" % ( vacation, )
return result
#TODO: DO Resource allocation and compute availability
def compute_allocation ( self , rc , uid , ids , start_date , end_date , context = None ) :
if context == None :
context = { }
allocation = { }
return allocation
def schedule_tasks ( self , cr , uid , ids , context = None ) :
context = context or { }
if type ( ids ) in ( long , int , ) :
ids = [ ids ]
projects = self . browse ( cr , uid , ids , context = context )
result = self . _schedule_header ( cr , uid , ids , False , context = context )
for project in projects :
result + = self . _schedule_project ( cr , uid , project , context = context )
result + = self . pool . get ( ' project.task ' ) . _generate_task ( cr , uid , project . tasks , ident = 4 , context = context )
local_dict = { }
exec result in local_dict
projects_gantt = Task . BalancedProject ( local_dict [ ' Project ' ] )
for project in projects :
project_gantt = getattr ( projects_gantt , ' Project_ %d ' % ( project . id , ) )
for task in project . tasks :
2013-06-26 13:25:19 +00:00
if task . stage_id and task . stage_id . fold :
2011-11-13 15:40:52 +00:00
continue
p = getattr ( project_gantt , ' Task_ %d ' % ( task . id , ) )
self . pool . get ( ' project.task ' ) . write ( cr , uid , [ task . id ] , {
' date_start ' : p . start . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
' date_end ' : p . end . strftime ( ' % Y- % m- %d % H: % M: % S ' )
} , context = context )
if ( not task . user_id ) and ( p . booked_resource ) :
self . pool . get ( ' project.task ' ) . write ( cr , uid , [ task . id ] , {
' user_id ' : int ( p . booked_resource [ 0 ] . name [ 5 : ] ) ,
} , context = context )
return True
2006-12-07 13:41:40 +00:00
2012-04-03 08:19:46 +00:00
def create ( self , cr , uid , vals , context = None ) :
2013-04-09 11:11:58 +00:00
if context is None :
context = { }
# Prevent double project creation when 'use_tasks' is checked + alias management
2013-05-16 16:42:07 +00:00
create_context = dict ( context , project_creation_in_progress = True ,
2013-07-19 09:00:58 +00:00
alias_model_name = vals . get ( ' alias_model ' , ' project.task ' ) ,
alias_parent_model_name = self . _name )
2013-04-09 11:11:58 +00:00
if vals . get ( ' type ' , False ) not in ( ' template ' , ' contract ' ) :
2012-12-10 19:28:10 +00:00
vals [ ' type ' ] = ' contract '
2013-04-09 11:11:58 +00:00
project_id = super ( project , self ) . create ( cr , uid , vals , context = create_context )
project_rec = self . browse ( cr , uid , project_id , context = context )
2013-05-16 16:42:07 +00:00
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 )
2012-08-06 00:44:17 +00:00
return project_id
2012-04-03 08:19:46 +00:00
2012-06-28 06:46:09 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
2012-08-06 00:44:17 +00:00
# if alias_model has been changed, update alias_model_id accordingly
2012-06-28 06:46:09 +00:00
if vals . get ( ' alias_model ' ) :
2012-08-06 00:44:17 +00:00
model_ids = self . pool . get ( ' ir.model ' ) . search ( cr , uid , [ ( ' model ' , ' = ' , vals . get ( ' alias_model ' , ' project.task ' ) ) ] )
2012-08-01 09:14:39 +00:00
vals . update ( alias_model_id = model_ids [ 0 ] )
2012-06-28 06:46:09 +00:00
return super ( project , self ) . write ( cr , uid , ids , vals , context = context )
2012-05-25 15:22:38 +00:00
2013-06-26 13:25:19 +00:00
class task ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = " project.task "
2010-05-19 18:32:32 +00:00
_description = " Task "
2008-07-22 15:11:28 +00:00
_date_name = " date_start "
2012-08-22 15:31:45 +00:00
_inherit = [ ' mail.thread ' , ' ir.needaction_mixin ' ]
2010-08-05 05:28:15 +00:00
2013-07-23 14:45:07 +00:00
_mail_post_access = ' read '
2012-12-18 15:36:01 +00:00
_track = {
' stage_id ' : {
2013-10-23 11:37:16 +00:00
# this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
' 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 ,
2012-12-18 15:36:01 +00:00
} ,
2013-07-10 12:25:26 +00:00
' user_id ' : {
' project.mt_task_assigned ' : lambda self , cr , uid , obj , ctx = None : obj . user_id and obj . user_id . id ,
} ,
2013-07-19 09:00:58 +00:00
' kanban_state ' : {
2013-06-26 13:25:19 +00:00
' project.mt_task_blocked ' : lambda self , cr , uid , obj , ctx = None : obj . kanban_state == ' blocked ' ,
2014-05-08 15:25:36 +00:00
' project.mt_task_ready ' : lambda self , cr , uid , obj , ctx = None : obj . kanban_state == ' done ' ,
2012-12-18 15:36:01 +00:00
} ,
}
2013-06-19 11:46:33 +00:00
def _get_default_partner ( self , cr , uid , context = None ) :
project_id = self . _get_default_project_id ( cr , uid , context )
if project_id :
2013-06-21 09:37:32 +00:00
project = self . pool . get ( ' project.project ' ) . browse ( cr , uid , project_id , context = context )
if project and project . partner_id :
2013-06-21 10:20:21 +00:00
return project . partner_id . id
2013-07-09 13:38:59 +00:00
return False
2013-06-19 11:46:33 +00:00
2012-05-30 14:53:07 +00:00
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 )
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 )
2013-10-23 11:37:16 +00:00
return self . stage_find ( cr , uid , [ ] , project_id , [ ( ' fold ' , ' = ' , False ) ] , context = context )
2011-11-13 12:07:15 +00:00
2011-11-21 16:49:40 +00:00
def _resolve_project_id_from_context ( self , cr , uid , context = None ) :
2012-05-25 15:22:38 +00:00
""" 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 .
2011-11-21 16:49:40 +00:00
"""
2013-06-26 13:25:19 +00:00
if context is None :
context = { }
2012-05-21 16:07:45 +00:00
if type ( context . get ( ' default_project_id ' ) ) in ( int , long ) :
2012-06-01 08:40:50 +00:00
return context [ ' default_project_id ' ]
2012-05-21 16:07:45 +00:00
if isinstance ( context . get ( ' default_project_id ' ) , basestring ) :
project_name = context [ ' default_project_id ' ]
2012-05-25 15:22:38 +00:00
project_ids = self . pool . get ( ' project.project ' ) . name_search ( cr , uid , name = project_name , context = context )
2011-11-21 16:49:40 +00:00
if len ( project_ids ) == 1 :
return project_ids [ 0 ] [ 0 ]
2012-05-25 15:22:38 +00:00
return None
2011-11-21 16:49:40 +00:00
2012-05-25 16:20:16 +00:00
def _read_group_stage_ids ( self , cr , uid , ids , domain , read_group_order = None , access_rights_uid = None , context = None ) :
2011-11-21 16:49:40 +00:00
stage_obj = self . pool . get ( ' project.task.type ' )
order = stage_obj . _order
2011-11-23 13:31:32 +00:00
access_rights_uid = access_rights_uid or uid
2012-05-25 16:20:16 +00:00
if read_group_order == ' stage_id desc ' :
2011-11-21 16:49:40 +00:00
order = ' %s desc ' % order
2012-05-25 16:20:16 +00:00
search_domain = [ ]
project_id = self . _resolve_project_id_from_context ( cr , uid , context = context )
2011-11-21 16:49:40 +00:00
if project_id :
2013-04-30 14:35:29 +00:00
search_domain + = [ ' | ' , ( ' project_ids ' , ' = ' , project_id ) ]
search_domain + = [ ( ' id ' , ' in ' , ids ) ]
2012-05-31 12:44:45 +00:00
stage_ids = stage_obj . _search ( cr , uid , search_domain , order = order , access_rights_uid = access_rights_uid , context = context )
2011-11-23 13:31:32 +00:00
result = stage_obj . name_get ( cr , access_rights_uid , stage_ids , context = context )
2011-11-21 16:49:40 +00:00
# restore order of the search
result . sort ( lambda x , y : cmp ( stage_ids . index ( x [ 0 ] ) , stage_ids . index ( y [ 0 ] ) ) )
2012-09-06 15:23:03 +00:00
fold = { }
for stage in stage_obj . browse ( cr , access_rights_uid , stage_ids , context = context ) :
fold [ stage . id ] = stage . fold or False
return result , fold
2011-11-21 16:49:40 +00:00
2011-11-23 13:31:32 +00:00
def _read_group_user_id ( self , cr , uid , ids , domain , read_group_order = None , access_rights_uid = None , context = None ) :
2011-11-18 13:15:11 +00:00
res_users = self . pool . get ( ' res.users ' )
2011-11-21 16:49:40 +00:00
project_id = self . _resolve_project_id_from_context ( cr , uid , context = context )
2011-11-23 13:31:32 +00:00
access_rights_uid = access_rights_uid or uid
2011-11-21 16:49:40 +00:00
if project_id :
2011-11-23 13:31:32 +00:00
ids + = self . pool . get ( ' project.project ' ) . read ( cr , access_rights_uid , project_id , [ ' members ' ] , context = context ) [ ' members ' ]
2011-11-21 16:49:40 +00:00
order = res_users . _order
# lame way to allow reverting search, should just work in the trivial case
if read_group_order == ' user_id desc ' :
order = ' %s desc ' % order
# de-duplicate and apply search order
2011-11-23 13:31:32 +00:00
ids = res_users . _search ( cr , uid , [ ( ' id ' , ' in ' , ids ) ] , order = order , access_rights_uid = access_rights_uid , context = context )
result = res_users . name_get ( cr , access_rights_uid , ids , context = context )
2011-11-21 16:49:40 +00:00
# restore order of the search
result . sort ( lambda x , y : cmp ( ids . index ( x [ 0 ] ) , ids . index ( y [ 0 ] ) ) )
2012-09-06 15:23:03 +00:00
return result , { }
2011-11-13 12:07:15 +00:00
_group_by_full = {
2012-05-25 16:20:16 +00:00
' stage_id ' : _read_group_stage_ids ,
2012-06-01 08:40:50 +00:00
' user_id ' : _read_group_user_id ,
2011-11-13 12:07:15 +00:00
}
2010-07-02 14:37:06 +00:00
def _str_get ( self , task , level = 0 , border = ' *** ' , context = None ) :
2008-07-22 15:11:28 +00:00
return border + ' ' + ( task . user_id and task . user_id . name . upper ( ) or ' ' ) + ( level and ( ' : L ' + str ( level ) ) or ' ' ) + ( ' - %.1f h / %.1f h ' % ( task . effective_hours or 0.0 , task . planned_hours ) ) + ' ' + border + ' \n ' + \
border [ 0 ] + ' ' + ( task . name or ' ' ) + ' \n ' + \
( task . description or ' ' ) + ' \n \n '
2010-08-05 05:28:15 +00:00
2010-01-31 21:29:06 +00:00
# Compute: effective_hours, total_hours, progress
2010-05-03 11:01:51 +00:00
def _hours_get ( self , cr , uid , ids , field_names , args , context = None ) :
res = { }
2010-06-10 13:34:19 +00:00
cr . execute ( " SELECT task_id, COALESCE(SUM(hours),0) FROM project_task_work WHERE task_id IN %s GROUP BY task_id " , ( tuple ( ids ) , ) )
2008-09-07 23:24:39 +00:00
hours = dict ( cr . fetchall ( ) )
for task in self . browse ( cr , uid , ids , context = context ) :
2010-10-17 20:31:19 +00:00
res [ task . id ] = { ' effective_hours ' : hours . get ( task . id , 0.0 ) , ' total_hours ' : ( task . remaining_hours or 0.0 ) + hours . get ( task . id , 0.0 ) }
res [ task . id ] [ ' delay_hours ' ] = res [ task . id ] [ ' total_hours ' ] - task . planned_hours
2010-05-27 09:57:03 +00:00
res [ task . id ] [ ' progress ' ] = 0.0
2015-06-30 13:42:24 +00:00
if not float_is_zero ( res [ task . id ] [ ' total_hours ' ] , precision_digits = 2 ) :
2010-08-03 12:38:19 +00:00
res [ task . id ] [ ' progress ' ] = round ( min ( 100.0 * hours . get ( task . id , 0.0 ) / res [ task . id ] [ ' total_hours ' ] , 99.99 ) , 2 )
2013-06-26 13:25:19 +00:00
if task . stage_id and task . stage_id . fold :
2010-01-31 21:29:06 +00:00
res [ task . id ] [ ' progress ' ] = 100.0
2008-07-22 15:11:28 +00:00
return res
2006-12-07 13:41:40 +00:00
2012-03-05 18:40:03 +00:00
def onchange_remaining ( self , cr , uid , ids , remaining = 0.0 , planned = 0.0 ) :
2010-10-04 18:47:57 +00:00
if remaining and not planned :
2013-07-10 12:25:26 +00:00
return { ' value ' : { ' planned_hours ' : remaining } }
2010-10-04 18:47:57 +00:00
return { }
2012-03-05 18:40:03 +00:00
def onchange_planned ( self , cr , uid , ids , planned = 0.0 , effective = 0.0 ) :
2013-07-10 12:25:26 +00:00
return { ' value ' : { ' remaining_hours ' : planned - effective } }
2011-03-02 21:43:56 +00:00
2013-06-19 11:46:33 +00:00
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 } }
2010-12-17 12:38:44 +00:00
return { }
2011-03-02 21:43:56 +00:00
2013-06-26 13:25:19 +00:00
def onchange_user_id ( self , cr , uid , ids , user_id , context = None ) :
vals = { }
if user_id :
2013-07-10 12:25:26 +00:00
vals [ ' date_start ' ] = fields . datetime . now ( )
2013-06-26 13:25:19 +00:00
return { ' value ' : vals }
2011-03-02 21:43:56 +00:00
def duplicate_task ( self , cr , uid , map_ids , context = None ) :
2014-02-06 17:10:28 +00:00
mapper = lambda t : map_ids . get ( t . id , t . id )
for task in self . browse ( cr , uid , map_ids . values ( ) , context ) :
new_child_ids = set ( map ( mapper , task . child_ids ) )
new_parent_ids = set ( map ( mapper , task . parent_ids ) )
if new_child_ids or new_parent_ids :
task . write ( { ' parent_ids ' : [ ( 6 , 0 , list ( new_parent_ids ) ) ] ,
' child_ids ' : [ ( 6 , 0 , list ( new_child_ids ) ) ] } )
2011-03-02 21:43:56 +00:00
2012-03-05 18:40:03 +00:00
def copy_data ( self , cr , uid , id , default = None , context = None ) :
if default is None :
default = { }
2015-10-09 16:57:46 +00:00
current = self . browse ( cr , uid , id , context = context )
2014-07-06 14:44:26 +00:00
if not default . get ( ' name ' ) :
default [ ' name ' ] = _ ( " %s (copy) " ) % current . name
2015-10-09 16:57:46 +00:00
if ' remaining_hours ' not in default :
default [ ' remaining_hours ' ] = current . planned_hours
2009-02-04 13:12:10 +00:00
return super ( task , self ) . copy_data ( cr , uid , id , default , context )
2014-08-21 10:53:47 +00:00
2010-07-18 19:43:57 +00:00
def _is_template ( self , cr , uid , ids , field_name , arg , context = None ) :
res = { }
for task in self . browse ( cr , uid , ids , context = context ) :
res [ task . id ] = True
if task . project_id :
if task . project_id . active == False or task . project_id . state == ' template ' :
res [ task . id ] = False
return res
2010-08-05 07:20:23 +00:00
2010-10-12 11:43:30 +00:00
def _get_task ( self , cr , uid , ids , context = None ) :
result = { }
for work in self . pool . get ( ' project.task.work ' ) . browse ( cr , uid , ids , context = context ) :
if work . task_id : result [ work . task_id . id ] = True
return result . keys ( )
2011-03-02 21:43:56 +00:00
2008-07-22 15:11:28 +00:00
_columns = {
2011-07-01 23:41:24 +00:00
' active ' : fields . function ( _is_template , store = True , string = ' Not a Template Task ' , type = ' boolean ' , help = " This field is computed automatically and have the same behavior than the boolean ' active ' field: if the task is linked to a template or unactivated project, it will be hidden unless specifically asked. " ) ,
2014-03-19 11:50:57 +00:00
' name ' : fields . char ( ' Task Summary ' , track_visibility = ' onchange ' , size = 128 , required = True , select = True ) ,
2008-07-22 15:11:28 +00:00
' description ' : fields . text ( ' Description ' ) ,
2014-02-14 11:49:37 +00:00
' priority ' : fields . selection ( [ ( ' 0 ' , ' Low ' ) , ( ' 1 ' , ' Normal ' ) , ( ' 2 ' , ' High ' ) ] , ' Priority ' , select = True ) ,
2011-12-09 06:03:08 +00:00
' sequence ' : fields . integer ( ' Sequence ' , select = True , help = " Gives the sequence order when displaying a list of tasks. " ) ,
2014-04-02 11:07:00 +00:00
' stage_id ' : fields . many2one ( ' project.task.type ' , ' Stage ' , track_visibility = ' onchange ' , select = True ,
2014-07-06 14:44:26 +00:00
domain = " [( ' project_ids ' , ' = ' , project_id)] " , copy = False ) ,
2012-08-30 12:12:56 +00:00
' categ_ids ' : fields . many2many ( ' project.category ' , string = ' Tags ' ) ,
2014-02-21 12:21:21 +00:00
' kanban_state ' : fields . selection ( [ ( ' normal ' , ' In Progress ' ) , ( ' blocked ' , ' Blocked ' ) , ( ' done ' , ' Ready for next stage ' ) ] , ' Kanban State ' ,
2012-12-20 11:47:30 +00:00
track_visibility = ' onchange ' ,
2011-11-21 16:52:47 +00:00
help = " A task ' s kanban state indicates special situations affecting it: \n "
" * Normal is the default situation \n "
" * Blocked indicates something is preventing the progress of this task \n "
2012-10-31 04:53:46 +00:00
" * Ready for next stage indicates the task is ready to be pulled to the next stage " ,
2014-07-06 14:44:26 +00:00
required = False , copy = False ) ,
2013-06-17 13:38:25 +00:00
' 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)
2014-07-06 14:44:26 +00:00
' date_start ' : fields . datetime ( ' Starting Date ' , select = True , copy = False ) ,
' date_end ' : fields . datetime ( ' Ending Date ' , select = True , copy = False ) ,
' date_deadline ' : fields . date ( ' Deadline ' , select = True , copy = False ) ,
' date_last_stage_update ' : fields . datetime ( ' Last Stage Update ' , select = True , copy = False ) ,
2014-04-03 15:59:04 +00:00
' project_id ' : fields . many2one ( ' project.project ' , ' Project ' , ondelete = ' set null ' , select = True , track_visibility = ' onchange ' , change_default = True ) ,
2010-01-12 05:32:48 +00:00
' parent_ids ' : fields . many2many ( ' project.task ' , ' project_task_parent_rel ' , ' task_id ' , ' parent_id ' , ' Parent Tasks ' ) ,
2010-02-15 15:31:31 +00:00
' child_ids ' : fields . many2many ( ' project.task ' , ' project_task_parent_rel ' , ' parent_id ' , ' task_id ' , ' Delegated Tasks ' ) ,
2008-07-22 15:11:28 +00:00
' notes ' : fields . text ( ' Notes ' ) ,
2012-07-12 14:46:00 +00:00
' planned_hours ' : fields . float ( ' Initially Planned Hours ' , help = ' Estimated time to do the task, usually set by the project manager when the task is in draft state. ' ) ,
2011-07-01 23:41:24 +00:00
' effective_hours ' : fields . function ( _hours_get , string = ' Hours Spent ' , multi = ' hours ' , help = " Computed using the sum of the task work done. " ,
2010-10-12 11:43:30 +00:00
store = {
2010-10-17 20:31:19 +00:00
' project.task ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' work_ids ' , ' remaining_hours ' , ' planned_hours ' ] , 10 ) ,
2010-10-12 11:43:30 +00:00
' project.task.work ' : ( _get_task , [ ' hours ' ] , 10 ) ,
} ) ,
2010-08-03 12:38:19 +00:00
' remaining_hours ' : fields . float ( ' Remaining Hours ' , digits = ( 16 , 2 ) , help = " Total remaining time, can be re-estimated periodically by the assignee of the task. " ) ,
2012-06-26 06:20:15 +00:00
' total_hours ' : fields . function ( _hours_get , string = ' Total ' , multi = ' hours ' , help = " Computed as: Time Spent + Remaining Time. " ,
2010-10-12 11:43:30 +00:00
store = {
2010-10-17 20:31:19 +00:00
' project.task ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' work_ids ' , ' remaining_hours ' , ' planned_hours ' ] , 10 ) ,
2010-10-12 11:43:30 +00:00
' project.task.work ' : ( _get_task , [ ' hours ' ] , 10 ) ,
} ) ,
2013-06-26 13:25:19 +00:00
' 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 " ,
2010-10-12 11:43:30 +00:00
store = {
2013-12-20 09:18:23 +00:00
' project.task ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' work_ids ' , ' remaining_hours ' , ' planned_hours ' , ' state ' , ' stage_id ' ] , 10 ) ,
2010-10-12 11:43:30 +00:00
' project.task.work ' : ( _get_task , [ ' hours ' ] , 10 ) ,
} ) ,
2011-12-02 08:43:12 +00:00
' delay_hours ' : fields . function ( _hours_get , string = ' Delay Hours ' , multi = ' hours ' , help = " Computed as difference between planned hours by the project manager and the total hours of the task. " ,
2010-10-12 11:43:30 +00:00
store = {
2010-10-17 20:31:19 +00:00
' project.task ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' work_ids ' , ' remaining_hours ' , ' planned_hours ' ] , 10 ) ,
2010-10-12 11:43:30 +00:00
' project.task.work ' : ( _get_task , [ ' hours ' ] , 10 ) ,
} ) ,
2014-06-11 08:40:39 +00:00
' reviewer_id ' : fields . many2one ( ' res.users ' , ' Reviewer ' , select = True , track_visibility = ' onchange ' ) ,
2014-04-02 11:07:00 +00:00
' user_id ' : fields . many2one ( ' res.users ' , ' Assigned to ' , select = True , track_visibility = ' onchange ' ) ,
2010-07-02 14:37:06 +00:00
' delegated_user_id ' : fields . related ( ' child_ids ' , ' user_id ' , type = ' many2one ' , relation = ' res.users ' , string = ' Delegated To ' ) ,
2012-09-29 19:58:25 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , ' Customer ' ) ,
2009-02-26 10:33:11 +00:00
' work_ids ' : fields . one2many ( ' project.task.work ' , ' task_id ' , ' Work done ' ) ,
2010-07-15 07:35:17 +00:00
' manager_id ' : fields . related ( ' project_id ' , ' analytic_account_id ' , ' user_id ' , type = ' many2one ' , relation = ' res.users ' , string = ' Project Manager ' ) ,
2009-11-10 12:49:20 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' ) ,
2011-10-12 11:11:42 +00:00
' id ' : fields . integer ( ' ID ' , readonly = True ) ,
2011-09-20 06:57:28 +00:00
' color ' : fields . integer ( ' Color Index ' ) ,
2012-08-10 14:43:39 +00:00
' user_email ' : fields . related ( ' user_id ' , ' email ' , type = ' char ' , string = ' User Email ' , readonly = True ) ,
2008-07-22 15:11:28 +00:00
}
_defaults = {
2012-05-30 14:53:07 +00:00
' stage_id ' : _get_default_stage_id ,
' project_id ' : _get_default_project_id ,
2013-09-19 14:23:38 +00:00
' date_last_stage_update ' : fields . datetime . now ,
2011-11-08 14:37:14 +00:00
' kanban_state ' : ' normal ' ,
2014-08-22 12:22:26 +00:00
' priority ' : ' 0 ' ,
2010-07-02 14:37:06 +00:00
' progress ' : 0 ,
' sequence ' : 10 ,
' active ' : True ,
2014-06-11 08:40:39 +00:00
' reviewer_id ' : lambda obj , cr , uid , ctx = None : uid ,
2013-06-19 11:46:33 +00:00
' 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 ) ,
2008-07-22 15:11:28 +00:00
}
2014-08-20 09:11:20 +00:00
_order = " priority desc, sequence, date_start, name, id "
2010-11-22 10:37:53 +00:00
def _check_recursion ( self , cr , uid , ids , context = None ) :
2011-03-02 22:52:59 +00:00
for id in ids :
visited_branch = set ( )
visited_node = set ( )
res = self . _check_cycle ( cr , uid , id , visited_branch , visited_node , context = context )
if not res :
return False
2011-03-02 21:43:56 +00:00
return True
2010-09-18 05:52:11 +00:00
2011-03-02 22:52:59 +00:00
def _check_cycle ( self , cr , uid , id , visited_branch , visited_node , context = None ) :
if id in visited_branch : #Cycle
2010-09-14 11:02:02 +00:00
return False
2010-09-18 05:52:11 +00:00
2011-03-02 22:52:59 +00:00
if id in visited_node : #Already tested don't work one more time for nothing
return True
visited_branch . add ( id )
visited_node . add ( id )
#visit child using DFS
task = self . browse ( cr , uid , id , context = context )
for child in task . child_ids :
res = self . _check_cycle ( cr , uid , child . id , visited_branch , visited_node , context = context )
if not res :
2010-09-14 11:02:02 +00:00
return False
2011-03-02 22:52:59 +00:00
visited_branch . remove ( id )
2010-09-14 11:02:02 +00:00
return True
2006-12-07 13:41:40 +00:00
2011-01-18 06:57:42 +00:00
def _check_dates ( self , cr , uid , ids , context = None ) :
if context == None :
context = { }
obj_task = self . browse ( cr , uid , ids [ 0 ] , context = context )
start = obj_task . date_start or False
end = obj_task . date_end or False
if start and end :
2011-03-02 21:43:56 +00:00
if start > end :
2011-01-18 06:57:42 +00:00
return False
2011-03-02 21:43:56 +00:00
return True
2011-01-18 06:57:42 +00:00
2010-01-20 12:44:10 +00:00
_constraints = [
2011-01-18 06:57:42 +00:00
( _check_recursion , ' Error ! You cannot create recursive tasks. ' , [ ' parent_ids ' ] ) ,
2015-03-06 16:06:12 +00:00
( _check_dates , ' Error ! Task end-date must be greater than task start-date ' , [ ' date_start ' , ' date_end ' ] )
2010-01-20 12:44:10 +00:00
]
2012-10-02 10:29:15 +00:00
2008-09-07 23:24:39 +00:00
# Override view according to the company definition
2009-09-24 10:46:21 +00:00
def fields_view_get ( self , cr , uid , view_id = None , view_type = ' form ' , context = None , toolbar = False , submenu = False ) :
2010-03-16 12:51:46 +00:00
users_obj = self . pool . get ( ' res.users ' )
2012-06-05 10:20:15 +00:00
if context is None : context = { }
2008-09-07 23:24:39 +00:00
2015-08-06 12:18:37 +00:00
res = super ( task , self ) . fields_view_get ( cr , uid , view_id , view_type , context = context , toolbar = toolbar , submenu = submenu )
2009-12-02 14:40:35 +00:00
2015-03-31 13:32:24 +00:00
# read uom as admin to avoid access rights issues, e.g. for portal/share users,
# this should be safe (no context passed to avoid side-effects)
obj_tm = users_obj . browse ( cr , SUPERUSER_ID , uid , context = context ) . company_id . project_time_mode_id
try :
# using get_object to get translation value
uom_hour = self . pool [ ' ir.model.data ' ] . get_object ( cr , uid , ' product ' , ' product_uom_hour ' , context = context )
except ValueError :
uom_hour = False
if not obj_tm or not uom_hour or obj_tm . id == uom_hour . id :
2008-09-07 23:24:39 +00:00
return res
2009-12-02 14:40:35 +00:00
2008-09-07 23:24:39 +00:00
eview = etree . fromstring ( res [ ' arch ' ] )
2010-01-08 11:05:05 +00:00
2015-03-31 13:32:24 +00:00
# if the project_time_mode_id is not in hours (so in days), display it as a float field
2009-12-02 14:40:35 +00:00
def _check_rec ( eview ) :
if eview . attrib . get ( ' widget ' , ' ' ) == ' float_time ' :
2008-09-07 23:24:39 +00:00
eview . set ( ' widget ' , ' float ' )
for child in eview :
2009-12-02 14:40:35 +00:00
_check_rec ( child )
2008-09-07 23:24:39 +00:00
return True
2010-01-08 11:05:05 +00:00
2009-12-02 14:40:35 +00:00
_check_rec ( eview )
2010-01-08 11:05:05 +00:00
2008-09-07 23:24:39 +00:00
res [ ' arch ' ] = etree . tostring ( eview )
2010-01-08 11:05:05 +00:00
2015-03-31 13:32:24 +00:00
# replace reference of 'Hours' to 'Day(s)'
2008-09-07 23:24:39 +00:00
for f in res [ ' fields ' ] :
2015-03-31 13:32:24 +00:00
# TODO this NOT work in different language than english
# the field 'Initially Planned Hours' should be replaced by 'Initially Planned Days'
# but string 'Initially Planned Days' is not available in translation
2008-09-07 23:24:39 +00:00
if ' Hours ' in res [ ' fields ' ] [ f ] [ ' string ' ] :
2015-03-31 13:32:24 +00:00
res [ ' fields ' ] [ f ] [ ' string ' ] = res [ ' fields ' ] [ f ] [ ' string ' ] . replace ( ' Hours ' , obj_tm . name )
2008-09-07 23:24:39 +00:00
return res
2013-03-21 13:31:39 +00:00
def get_empty_list_help ( self , cr , uid , help , context = None ) :
2014-07-06 14:44:26 +00:00
context = dict ( context or { } )
2013-03-27 16:11:07 +00:00
context [ ' empty_list_help_id ' ] = context . get ( ' default_project_id ' )
context [ ' empty_list_help_model ' ] = ' project.project '
2013-03-21 13:31:39 +00:00
context [ ' empty_list_help_document_name ' ] = _ ( " tasks " )
return super ( task , self ) . get_empty_list_help ( cr , uid , help , context = context )
2013-02-06 09:44:14 +00:00
2012-10-02 10:29:15 +00:00
# ----------------------------------------
2012-05-25 15:22:38 +00:00
# Case management
2012-10-02 10:29:15 +00:00
# ----------------------------------------
2012-05-25 15:22:38 +00:00
def stage_find ( self , cr , uid , cases , section_id , domain = [ ] , order = ' sequence ' , context = None ) :
""" Override of the base.stage method
Parameter of the stage search taken from the lead :
- section_id : if set , stages must belong to this section or
be a default stage ; if not set , stages must be default
stages
"""
if isinstance ( cases , ( int , long ) ) :
cases = self . browse ( cr , uid , cases , context = context )
2012-05-31 12:44:45 +00:00
# collect all section_ids
section_ids = [ ]
2012-05-25 15:22:38 +00:00
if section_id :
2012-05-31 12:44:45 +00:00
section_ids . append ( section_id )
2012-05-25 16:20:16 +00:00
for task in cases :
2012-05-31 12:44:45 +00:00
if task . project_id :
section_ids . append ( task . project_id . id )
search_domain = [ ]
if section_ids :
2013-06-26 13:25:19 +00:00
search_domain = [ ( ' | ' ) ] * ( len ( section_ids ) - 1 )
2012-05-31 12:44:45 +00:00
for section_id in section_ids :
2012-05-31 14:19:30 +00:00
search_domain . append ( ( ' project_ids ' , ' = ' , section_id ) )
2013-07-10 12:25:26 +00:00
search_domain + = list ( domain )
2012-05-31 12:44:45 +00:00
# perform search, return the first found
stage_ids = self . pool . get ( ' project.task.type ' ) . search ( cr , uid , search_domain , order = order , context = context )
2012-05-25 15:22:38 +00:00
if stage_ids :
return stage_ids [ 0 ]
return False
2011-06-22 06:19:23 +00:00
def _check_child_task ( self , cr , uid , ids , context = None ) :
if context == None :
context = { }
tasks = self . browse ( cr , uid , ids , context = context )
for task in tasks :
if task . child_ids :
for child in task . child_ids :
2013-06-26 13:25:19 +00:00
if child . stage_id and not child . stage_id . fold :
2013-04-29 07:15:57 +00:00
raise osv . except_osv ( _ ( " Warning! " ) , _ ( " Child task still open. \n Please cancel or complete child task first. " ) )
2011-06-22 06:19:23 +00:00
return True
2011-11-25 08:53:25 +00:00
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 )
new_attachment_ids = [ ]
for attachment_id in attachment_ids :
new_attachment_ids . append ( attachment . copy ( cr , uid , attachment_id , default = { ' res_id ' : delegated_task_id } , context = context ) )
return new_attachment_ids
2012-03-05 18:40:03 +00:00
def do_delegate ( self , cr , uid , ids , delegate_data = None , context = None ) :
2010-08-27 12:05:25 +00:00
"""
Delegate Task to another users .
"""
2012-03-05 18:40:03 +00:00
if delegate_data is None :
delegate_data = { }
2011-11-17 15:09:14 +00:00
assert delegate_data [ ' user_id ' ] , _ ( " Delegated User should be specified " )
2011-11-25 08:53:25 +00:00
delegated_tasks = { }
2011-11-17 15:09:14 +00:00
for task in self . browse ( cr , uid , ids , context = context ) :
2011-11-25 08:53:25 +00:00
delegated_task_id = self . copy ( cr , uid , task . id , {
2011-11-17 15:09:14 +00:00
' name ' : delegate_data [ ' name ' ] ,
' project_id ' : delegate_data [ ' project_id ' ] and delegate_data [ ' project_id ' ] [ 0 ] or False ,
2013-07-10 12:25:26 +00:00
' stage_id ' : delegate_data . get ( ' stage_id ' ) and delegate_data . get ( ' stage_id ' ) [ 0 ] or False ,
2011-11-17 15:09:14 +00:00
' 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 ] ) ] ,
' description ' : delegate_data [ ' new_task_description ' ] or ' ' ,
' child_ids ' : [ ] ,
' work_ids ' : [ ]
} , context = context )
2011-11-25 08:53:25 +00:00
self . _delegate_task_attachments ( cr , uid , task . id , delegated_task_id , context = context )
2011-11-17 15:09:14 +00:00
newname = delegate_data [ ' prefix ' ] or ' '
task . write ( {
' remaining_hours ' : delegate_data [ ' planned_hours_me ' ] ,
' planned_hours ' : delegate_data [ ' planned_hours_me ' ] + ( task . effective_hours or 0.0 ) ,
' name ' : newname ,
} , context = context )
2011-11-25 08:53:25 +00:00
delegated_tasks [ task . id ] = delegated_task_id
return delegated_tasks
2010-08-27 12:05:25 +00:00
2011-12-13 04:33:44 +00:00
def set_remaining_time ( self , cr , uid , ids , remaining_time = 1.0 , context = None ) :
for task in self . browse ( cr , uid , ids , context = context ) :
2013-10-23 11:37:16 +00:00
if ( task . stage_id and task . stage_id . sequence < = 1 ) or ( task . planned_hours == 0.0 ) :
2014-11-20 12:25:00 +00:00
self . write ( cr , uid , [ task . id ] , { ' planned_hours ' : remaining_time + task . effective_hours } , context = context )
2011-12-13 04:33:44 +00:00
self . write ( cr , uid , ids , { ' remaining_hours ' : remaining_time } , context = context )
2011-11-07 17:36:53 +00:00
return True
2011-12-13 04:33:44 +00:00
def set_remaining_time_1 ( self , cr , uid , ids , context = None ) :
return self . set_remaining_time ( cr , uid , ids , 1.0 , context )
2011-11-08 17:03:08 +00:00
def set_remaining_time_2 ( self , cr , uid , ids , context = None ) :
2011-12-13 04:33:44 +00:00
return self . set_remaining_time ( cr , uid , ids , 2.0 , context )
2011-11-07 17:36:53 +00:00
2011-11-08 17:03:08 +00:00
def set_remaining_time_5 ( self , cr , uid , ids , context = None ) :
2011-12-13 04:33:44 +00:00
return self . set_remaining_time ( cr , uid , ids , 5.0 , context )
2011-11-07 17:36:53 +00:00
2011-11-08 17:03:08 +00:00
def set_remaining_time_10 ( self , cr , uid , ids , context = None ) :
2011-12-13 04:33:44 +00:00
return self . set_remaining_time ( cr , uid , ids , 10.0 , context )
2011-11-07 17:36:53 +00:00
2011-12-03 14:04:27 +00:00
def _store_history ( self , cr , uid , ids , context = None ) :
for task in self . browse ( cr , uid , ids , context = context ) :
self . pool . get ( ' project.task.history ' ) . create ( cr , uid , {
' task_id ' : task . id ,
' remaining_hours ' : task . remaining_hours ,
2011-12-13 04:33:44 +00:00
' planned_hours ' : task . planned_hours ,
2011-12-03 14:04:27 +00:00
' kanban_state ' : task . kanban_state ,
2012-05-25 16:20:16 +00:00
' type_id ' : task . stage_id . id ,
2011-12-03 14:04:27 +00:00
' user_id ' : task . user_id . id
} , context = context )
return True
2013-06-26 13:25:19 +00:00
# ------------------------------------------------
# CRUD overrides
# ------------------------------------------------
2012-11-02 15:24:30 +00:00
def create ( self , cr , uid , vals , context = None ) :
2014-07-06 14:44:26 +00:00
context = dict ( context or { } )
2013-07-09 12:43:24 +00:00
# for default stage
2013-06-19 11:46:33 +00:00
if vals . get ( ' project_id ' ) and not context . get ( ' default_project_id ' ) :
context [ ' default_project_id ' ] = vals . get ( ' project_id ' )
2013-06-26 13:25:19 +00:00
# user_id change: update date_start
2014-10-06 03:27:55 +00:00
if vals . get ( ' user_id ' ) and not vals . get ( ' date_start ' ) :
2013-06-26 13:25:19 +00:00
vals [ ' date_start ' ] = fields . datetime . now ( )
2013-05-29 13:14:58 +00:00
# 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 )
2012-04-03 08:38:15 +00:00
self . _store_history ( cr , uid , [ task_id ] , context = context )
return task_id
2011-12-03 14:04:27 +00:00
2011-11-21 16:52:47 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2013-07-09 12:43:24 +00:00
2013-06-26 13:25:19 +00:00
# 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
2014-04-04 13:39:58 +00:00
if vals . get ( ' user_id ' ) and ' date_start ' not in vals :
2013-06-26 13:25:19 +00:00
vals [ ' date_start ' ] = fields . datetime . now ( )
# Overridden to reset the kanban_state to normal whenever
# the stage (stage_id) of the task changes.
2012-05-25 16:20:16 +00:00
if vals and not ' kanban_state ' in vals and ' stage_id ' in vals :
new_stage = vals . get ( ' stage_id ' )
2011-11-21 16:52:47 +00:00
vals_reset_kstate = dict ( vals , kanban_state = ' normal ' )
for t in self . browse ( cr , uid , ids , context = context ) :
2014-07-06 14:44:26 +00:00
write_vals = vals_reset_kstate if t . stage_id . id != new_stage else vals
2012-11-02 15:24:30 +00:00
super ( task , self ) . write ( cr , uid , [ t . id ] , write_vals , context = context )
2011-12-03 14:04:27 +00:00
result = True
else :
2012-11-02 15:24:30 +00:00
result = super ( task , self ) . write ( cr , uid , ids , vals , context = context )
2013-06-26 13:25:19 +00:00
if any ( item in vals for item in [ ' stage_id ' , ' remaining_hours ' , ' user_id ' , ' kanban_state ' ] ) :
2011-12-03 14:04:27 +00:00
self . _store_history ( cr , uid , ids , context = context )
return result
2011-11-21 16:52:47 +00:00
2011-06-22 06:19:23 +00:00
def unlink ( self , cr , uid , ids , context = None ) :
if context == None :
context = { }
self . _check_child_task ( cr , uid , ids , context = context )
res = super ( task , self ) . unlink ( cr , uid , ids , context )
return res
2008-06-17 13:35:06 +00:00
2011-11-13 15:40:52 +00:00
def _generate_task ( self , cr , uid , tasks , ident = 4 , context = None ) :
context = context or { }
result = " "
ident = ' ' * ident
2015-04-23 15:01:13 +00:00
company = self . pool [ " res.users " ] . browse ( cr , uid , uid , context = context ) . company_id
duration_uom = {
' day(s) ' : ' d ' , ' days ' : ' d ' , ' day ' : ' d ' , ' d ' : ' d ' ,
' month(s) ' : ' m ' , ' months ' : ' m ' , ' month ' : ' month ' , ' m ' : ' m ' ,
' week(s) ' : ' w ' , ' weeks ' : ' w ' , ' week ' : ' w ' , ' w ' : ' w ' ,
2015-04-24 09:19:34 +00:00
' hour(s) ' : ' H ' , ' hours ' : ' H ' , ' hour ' : ' H ' , ' h ' : ' H ' ,
2015-04-23 15:01:13 +00:00
} . get ( company . project_time_mode_id . name . lower ( ) , " hour(s) " )
2011-11-13 15:40:52 +00:00
for task in tasks :
2013-06-26 13:25:19 +00:00
if task . stage_id and task . stage_id . fold :
2011-11-13 15:40:52 +00:00
continue
result + = '''
% sdef Task_ % s ( ) :
2015-04-23 15:01:13 +00:00
% s todo = \" %.2f %s \"
% s effort = \" %.2f %s \" ' ' ' % (ident, task.id, ident, task.remaining_hours, duration_uom, ident, task.total_hours, duration_uom)
2011-11-13 15:40:52 +00:00
start = [ ]
for t2 in task . parent_ids :
start . append ( " up.Task_ %s .end " % ( t2 . id , ) )
if start :
result + = '''
% s start = max ( % s )
''' % (ident, ' , ' .join(start))
if task . user_id :
result + = '''
% s resource = % s
''' % (ident, ' User_ ' +str(task.user_id.id))
result + = " \n "
return result
2012-09-04 09:29:38 +00:00
# ---------------------------------------------------
2012-10-02 10:29:15 +00:00
# Mail gateway
2012-09-04 09:29:38 +00:00
# ---------------------------------------------------
2014-06-11 08:40:39 +00:00
def _message_get_auto_subscribe_fields ( self , cr , uid , updated_fields , auto_follow_fields = None , context = None ) :
if auto_follow_fields is None :
auto_follow_fields = [ ' user_id ' , ' reviewer_id ' ]
return super ( task , self ) . _message_get_auto_subscribe_fields ( cr , uid , updated_fields , auto_follow_fields , context = context )
2013-01-03 17:03:16 +00:00
def message_get_reply_to ( self , cr , uid , ids , context = None ) :
""" Override to get the reply_to of the parent project. """
2014-06-20 11:38:22 +00:00
tasks = self . browse ( cr , SUPERUSER_ID , ids , context = context )
project_ids = set ( [ task . project_id . id for task in tasks if task . project_id ] )
aliases = self . pool [ ' project.project ' ] . message_get_reply_to ( cr , uid , list ( project_ids ) , context = context )
return dict ( ( task . id , aliases . get ( task . project_id and task . project_id . id or 0 , False ) ) for task in tasks )
2013-01-03 17:03:16 +00:00
2012-09-04 09:29:38 +00:00
def message_new ( self , cr , uid , msg , custom_values = None , context = None ) :
""" Override to updates the document according to the email. """
2013-11-04 09:27:31 +00:00
if custom_values is None :
custom_values = { }
2013-01-10 17:27:23 +00:00
defaults = {
2012-10-09 10:35:35 +00:00
' name ' : msg . get ( ' subject ' ) ,
2012-09-04 09:29:38 +00:00
' planned_hours ' : 0.0 ,
2013-01-10 17:27:23 +00:00
}
defaults . update ( custom_values )
2013-05-28 14:44:47 +00:00
return super ( task , self ) . message_new ( cr , uid , msg , custom_values = defaults , context = context )
2012-09-04 09:29:38 +00:00
def message_update ( self , cr , uid , ids , msg , update_vals = None , context = None ) :
""" Override to update the task according to the email. """
2013-11-04 09:27:31 +00:00
if update_vals is None :
update_vals = { }
2012-09-04 09:29:38 +00:00
maps = {
2013-11-04 09:27:31 +00:00
' cost ' : ' planned_hours ' ,
2012-09-04 09:29:38 +00:00
}
for line in msg [ ' body ' ] . split ( ' \n ' ) :
line = line . strip ( )
2012-11-19 13:25:09 +00:00
res = tools . command_re . match ( line )
2012-09-04 09:29:38 +00:00
if res :
match = res . group ( 1 ) . lower ( )
field = maps . get ( match )
if field :
try :
update_vals [ field ] = float ( res . group ( 2 ) . lower ( ) )
except ( ValueError , TypeError ) :
pass
2013-11-04 09:27:31 +00:00
return super ( task , self ) . message_update ( cr , uid , ids , msg , update_vals = update_vals , context = context )
2012-09-04 09:29:38 +00:00
2006-12-07 13:41:40 +00:00
class project_work ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = " project.task.work "
2010-08-05 07:20:23 +00:00
_description = " Project Task Work "
2008-07-22 15:11:28 +00:00
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Work summary ' ) ,
2011-06-15 15:35:22 +00:00
' date ' : fields . datetime ( ' Date ' , select = " 1 " ) ,
' task_id ' : fields . many2one ( ' project.task ' , ' Task ' , ondelete = ' cascade ' , required = True , select = " 1 " ) ,
2008-09-08 22:53:40 +00:00
' hours ' : fields . float ( ' Time Spent ' ) ,
2011-06-15 15:35:22 +00:00
' user_id ' : fields . many2one ( ' res.users ' , ' Done by ' , required = True , select = " 1 " ) ,
2011-01-06 11:32:21 +00:00
' company_id ' : fields . related ( ' task_id ' , ' company_id ' , type = ' many2one ' , relation = ' res.company ' , string = ' Company ' , store = True , readonly = True )
2008-07-22 15:11:28 +00:00
}
2010-07-27 12:43:02 +00:00
2008-07-22 15:11:28 +00:00
_defaults = {
2010-03-16 12:51:46 +00:00
' user_id ' : lambda obj , cr , uid , context : uid ,
2010-11-02 11:29:09 +00:00
' date ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' )
2008-07-22 15:11:28 +00:00
}
2010-07-27 12:43:02 +00:00
2008-07-22 15:11:28 +00:00
_order = " date desc "
2014-07-06 14:44:26 +00:00
def create ( self , cr , uid , vals , context = None ) :
2010-10-17 23:33:01 +00:00
if ' hours ' in vals and ( not vals [ ' hours ' ] ) :
vals [ ' hours ' ] = 0.00
if ' task_id ' in vals :
cr . execute ( ' update project_task set remaining_hours=remaining_hours - %s where id= %s ' , ( vals . get ( ' hours ' , 0.0 ) , vals [ ' task_id ' ] ) )
2014-07-06 14:44:26 +00:00
self . pool . get ( ' project.task ' ) . invalidate_cache ( cr , uid , [ ' remaining_hours ' ] , [ vals [ ' task_id ' ] ] , context = context )
return super ( project_work , self ) . create ( cr , uid , vals , context = context )
2010-10-17 23:33:01 +00:00
2010-11-22 10:37:53 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
2010-10-17 23:33:01 +00:00
if ' hours ' in vals and ( not vals [ ' hours ' ] ) :
vals [ ' hours ' ] = 0.00
if ' hours ' in vals :
2014-07-06 14:44:26 +00:00
task_obj = self . pool . get ( ' project.task ' )
2010-11-22 10:37:53 +00:00
for work in self . browse ( cr , uid , ids , context = context ) :
2010-10-17 23:33:01 +00:00
cr . execute ( ' update project_task set remaining_hours=remaining_hours - %s + ( %s ) where id= %s ' , ( vals . get ( ' hours ' , 0.0 ) , work . hours , work . task_id . id ) )
2014-07-06 14:44:26 +00:00
task_obj . invalidate_cache ( cr , uid , [ ' remaining_hours ' ] , [ work . task_id . id ] , context = context )
2010-10-17 23:33:01 +00:00
return super ( project_work , self ) . write ( cr , uid , ids , vals , context )
2010-08-05 07:20:23 +00:00
2014-07-14 08:10:37 +00:00
def unlink ( self , cr , uid , ids , context = None ) :
2014-07-06 14:44:26 +00:00
task_obj = self . pool . get ( ' project.task ' )
2010-10-17 23:33:01 +00:00
for work in self . browse ( cr , uid , ids ) :
cr . execute ( ' update project_task set remaining_hours=remaining_hours + %s where id= %s ' , ( work . hours , work . task_id . id ) )
2014-07-06 14:44:26 +00:00
task_obj . invalidate_cache ( cr , uid , [ ' remaining_hours ' ] , [ work . task_id . id ] , context = context )
2014-07-14 08:10:37 +00:00
return super ( project_work , self ) . unlink ( cr , uid , ids , context = context )
2006-12-07 13:41:40 +00:00
2010-07-01 09:08:44 +00:00
class account_analytic_account ( osv . osv ) :
_inherit = ' account.analytic.account '
_description = ' Analytic Account '
2012-05-28 12:30:54 +00:00
_columns = {
2012-11-02 17:42:00 +00:00
' use_tasks ' : fields . boolean ( ' Tasks ' , help = " If checked, this contract will be available in the project menu and you will be able to manage tasks or track issues " ) ,
2012-05-28 12:30:54 +00:00
' company_uom_id ' : fields . related ( ' company_id ' , ' project_time_mode_id ' , type = ' many2one ' , relation = ' product.uom ' ) ,
}
2012-08-03 17:26:36 +00:00
2014-04-01 14:42:05 +00:00
def on_change_template ( self , cr , uid , ids , template_id , date_start = False , context = None ) :
res = super ( account_analytic_account , self ) . on_change_template ( cr , uid , ids , template_id , date_start = date_start , context = context )
2012-07-23 12:07:54 +00:00
if template_id and ' value ' in res :
template = self . browse ( cr , uid , template_id , context = context )
res [ ' value ' ] [ ' use_tasks ' ] = template . use_tasks
return res
2012-06-13 10:07:31 +00:00
def _trigger_project_creation ( self , cr , uid , vals , context = None ) :
'''
This function is used to decide if a project needs to be automatically created or not when an analytic account is created . It returns True if it needs to be so , False otherwise .
'''
2012-08-08 18:15:22 +00:00
if context is None : context = { }
return vals . get ( ' use_tasks ' ) and not ' project_creation_in_progress ' in context
2012-06-13 10:07:31 +00:00
def project_create ( self , cr , uid , analytic_account_id , vals , context = None ) :
'''
This function is called at the time of analytic account creation and is used to create a project automatically linked to it if the conditions are meet .
'''
2012-05-28 12:30:54 +00:00
project_pool = self . pool . get ( ' project.project ' )
2012-06-18 11:56:34 +00:00
project_id = project_pool . search ( cr , uid , [ ( ' analytic_account_id ' , ' = ' , analytic_account_id ) ] )
2012-06-14 10:09:22 +00:00
if not project_id and self . _trigger_project_creation ( cr , uid , vals , context = context ) :
2012-06-13 10:07:31 +00:00
project_values = {
' name ' : vals . get ( ' name ' ) ,
' analytic_account_id ' : analytic_account_id ,
2012-12-10 19:28:10 +00:00
' type ' : vals . get ( ' type ' , ' contract ' ) ,
2012-06-13 10:07:31 +00:00
}
return project_pool . create ( cr , uid , project_values , context = context )
return False
2010-07-01 09:08:44 +00:00
def create ( self , cr , uid , vals , context = None ) :
if context is None :
context = { }
if vals . get ( ' child_ids ' , False ) and context . get ( ' analytic_project_copy ' , False ) :
vals [ ' child_ids ' ] = [ ]
2012-05-28 12:30:54 +00:00
analytic_account_id = super ( account_analytic_account , self ) . create ( cr , uid , vals , context = context )
2012-06-13 10:07:31 +00:00
self . project_create ( cr , uid , analytic_account_id , vals , context = context )
2012-05-28 12:30:54 +00:00
return analytic_account_id
2011-12-09 07:30:04 +00:00
2012-06-18 11:56:34 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
2013-11-22 16:55:04 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2013-03-06 17:28:56 +00:00
vals_for_project = vals . copy ( )
2012-06-18 11:56:34 +00:00
for account in self . browse ( cr , uid , ids , context = context ) :
2013-03-06 17:28:56 +00:00
if not vals . get ( ' name ' ) :
vals_for_project [ ' name ' ] = account . name
2012-12-19 13:43:53 +00:00
if not vals . get ( ' type ' ) :
2013-03-06 17:28:56 +00:00
vals_for_project [ ' type ' ] = account . type
self . project_create ( cr , uid , account . id , vals_for_project , context = context )
2012-06-18 11:56:34 +00:00
return super ( account_analytic_account , self ) . write ( cr , uid , ids , vals , context = context )
2011-12-09 07:30:04 +00:00
2011-05-04 05:15:43 +00:00
def unlink ( self , cr , uid , ids , * args , * * kwargs ) :
project_obj = self . pool . get ( ' project.project ' )
analytic_ids = project_obj . search ( cr , uid , [ ( ' analytic_account_id ' , ' in ' , ids ) ] )
if analytic_ids :
2012-08-07 11:34:14 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' Please delete the project linked with this account first. ' ) )
2011-05-04 05:15:43 +00:00
return super ( account_analytic_account , self ) . unlink ( cr , uid , ids , * args , * * kwargs )
2010-07-01 09:08:44 +00:00
2013-08-28 14:11:23 +00:00
def name_search ( self , cr , uid , name , args = None , operator = ' ilike ' , context = None , limit = 100 ) :
if args is None :
args = [ ]
if context is None :
context = { }
if context . get ( ' current_model ' ) == ' project.project ' :
project_ids = self . search ( cr , uid , args + [ ( ' name ' , operator , name ) ] , limit = limit , context = context )
return self . name_get ( cr , uid , project_ids , context = context )
return super ( account_analytic_account , self ) . name_search ( cr , uid , name , args = args , operator = operator , context = context , limit = limit )
2012-08-06 20:07:10 +00:00
class project_project ( osv . osv ) :
_inherit = ' project.project '
_defaults = {
' use_tasks ' : True
}
2011-12-03 14:04:27 +00:00
class project_task_history ( osv . osv ) :
2012-10-10 11:16:49 +00:00
"""
Tasks History , used for cumulative flow charts ( Lean / Agile )
"""
2011-12-03 14:04:27 +00:00
_name = ' project.task.history '
_description = ' History of Tasks '
_rec_name = ' task_id '
_log_access = False
2012-10-10 11:16:49 +00:00
2011-12-03 14:04:27 +00:00
def _get_date ( self , cr , uid , ids , name , arg , context = None ) :
result = { }
for history in self . browse ( cr , uid , ids , context = context ) :
2013-06-26 14:22:42 +00:00
if history . type_id and history . type_id . fold :
2011-12-03 14:04:27 +00:00
result [ history . id ] = history . date
continue
cr . execute ( ''' select
date
from
project_task_history
where
task_id = % s and
id > % s
order by id limit 1 ''' , (history.task_id.id, history.id))
res = cr . fetchone ( )
result [ history . id ] = res and res [ 0 ] or False
return result
def _get_related_date ( self , cr , uid , ids , context = None ) :
result = [ ]
for history in self . browse ( cr , uid , ids , context = context ) :
cr . execute ( ''' select
id
2012-04-02 13:24:45 +00:00
from
2011-12-03 14:04:27 +00:00
project_task_history
where
task_id = % s and
id < % s
order by id desc limit 1 ''' , (history.task_id.id, history.id))
res = cr . fetchone ( )
if res :
result . append ( res [ 0 ] )
return result
_columns = {
' task_id ' : fields . many2one ( ' project.task ' , ' Task ' , ondelete = ' cascade ' , required = True , select = True ) ,
' type_id ' : fields . many2one ( ' project.task.type ' , ' Stage ' ) ,
2013-06-26 14:22:42 +00:00
' kanban_state ' : fields . selection ( [ ( ' normal ' , ' Normal ' ) , ( ' blocked ' , ' Blocked ' ) , ( ' done ' , ' Ready for next stage ' ) ] , ' Kanban State ' , required = False ) ,
2011-12-03 14:04:27 +00:00
' 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 )
} ) ,
2013-06-26 14:22:42 +00:00
' remaining_hours ' : fields . float ( ' Remaining Time ' , digits = ( 16 , 2 ) ) ,
' planned_hours ' : fields . float ( ' Planned Time ' , digits = ( 16 , 2 ) ) ,
2011-12-03 14:04:27 +00:00
' user_id ' : fields . many2one ( ' res.users ' , ' Responsible ' ) ,
}
_defaults = {
2012-02-13 18:07:41 +00:00
' date ' : fields . date . context_today ,
2011-12-03 14:04:27 +00:00
}
2012-05-29 08:24:58 +00:00
2011-12-03 14:04:27 +00:00
class project_task_history_cumulative ( osv . osv ) :
_name = ' project.task.history.cumulative '
_table = ' project_task_history_cumulative '
_inherit = ' project.task.history '
_auto = False
2012-10-10 11:16:49 +00:00
2011-12-03 14:04:27 +00:00
_columns = {
' end_date ' : fields . date ( ' End Date ' ) ,
2014-08-21 10:53:47 +00:00
' nbr_tasks ' : fields . integer ( ' # of Tasks ' , readonly = True ) ,
2012-10-10 11:16:49 +00:00
' project_id ' : fields . many2one ( ' project.project ' , ' Project ' ) ,
2011-12-03 14:04:27 +00:00
}
2012-10-10 11:16:49 +00:00
2011-12-03 14:04:27 +00:00
def init ( self , cr ) :
2012-10-12 13:54:29 +00:00
tools . drop_view_if_exists ( cr , ' project_task_history_cumulative ' )
2012-10-11 14:04:30 +00:00
cr . execute ( """ CREATE VIEW project_task_history_cumulative AS (
2011-12-03 14:04:27 +00:00
SELECT
2012-10-10 11:16:49 +00:00
history . date : : varchar | | ' - ' | | history . history_id : : varchar AS id ,
history . date AS end_date ,
2011-12-03 14:04:27 +00:00
*
FROM (
SELECT
2012-10-10 11:16:49 +00:00
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 ,
2013-06-26 14:22:42 +00:00
h . task_id , h . type_id , h . user_id , h . kanban_state ,
2014-08-21 10:53:47 +00:00
count ( h . task_id ) as nbr_tasks ,
2012-10-10 11:16:49 +00:00
greatest ( h . remaining_hours , 1 ) AS remaining_hours , greatest ( h . planned_hours , 1 ) AS planned_hours ,
t . project_id
2011-12-03 14:04:27 +00:00
FROM
2012-10-10 11:16:49 +00:00
project_task_history AS h
JOIN project_task AS t ON ( h . task_id = t . id )
2014-08-21 10:53:47 +00:00
GROUP BY
h . id ,
h . task_id ,
t . project_id
2012-10-10 11:16:49 +00:00
) AS history
2011-12-03 14:04:27 +00:00
)
""" )
2012-07-11 13:21:30 +00:00
class project_category ( osv . osv ) :
""" Category of project ' s task (or issue) """
_name = " project.category "
_description = " Category of project ' s task, issue, ... "
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Name ' , required = True , translate = True ) ,
2012-07-11 13:21:30 +00:00
}
2013-03-06 17:28:56 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: