'priority': fields.integer('Sequence', help="Gives the sequence order when displaying the list of projects"),
'warn_manager': fields.boolean('Warn Manager', help="If you check this field, the project manager will receive a request each time a task is completed by his team.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
- 'members': fields.many2many('res.users', 'project_user_rel', 'project_id', 'uid', 'Project Members', help="Project's member. Not used in any computation, just for information purpose, but a user has to be member of a project to add a the to this project.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
+ '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)]}),
'tasks': fields.one2many('project.task', 'project_id', "Project tasks"),
'planned_hours': fields.function(_progress_rate, multi="progress", method=True, string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.",
store = {
'project.project': (lambda self, cr, uid, ids, c={}: ids, ['tasks'], 10),
'project.task': (_get_project_task, ['planned_hours', 'effective_hours', 'remaining_hours', 'total_hours', 'progress', 'delay_hours','state'], 10),
}),
- 'effective_hours': fields.function(_progress_rate, multi="progress", method=True, string='Time Spent', help="Sum of spent hours of all tasks related to this project and its child projects.",
- store = {
- 'project.project': (lambda self, cr, uid, ids, c={}: ids, ['tasks'], 10),
- 'project.task': (_get_project_task, ['planned_hours', 'effective_hours', 'remaining_hours', 'total_hours', 'progress', 'delay_hours','state'], 10),
- }),
+ 'effective_hours': fields.function(_progress_rate, multi="progress", method=True, string='Time Spent', help="Sum of spent hours of all tasks related to this project and its child projects."),
'total_hours': fields.function(_progress_rate, multi="progress", method=True, string='Total Time', help="Sum of total hours of all tasks related to this project and its child projects.",
store = {
'project.project': (lambda self, cr, uid, ids, c={}: ids, ['tasks'], 10),
'project.task': (_get_project_task, ['planned_hours', 'effective_hours', 'remaining_hours', 'total_hours', 'progress', 'delay_hours','state'], 10),
}),
- 'progress_rate': fields.function(_progress_rate, multi="progress", method=True, string='Progress', type='float', group_operator="avg", help="Percent of tasks closed according to the total of tasks todo.",
- store = {
- 'project.project': (lambda self, cr, uid, ids, c={}: ids, ['tasks'], 10),
- 'project.task': (_get_project_task, ['planned_hours', 'effective_hours', 'remaining_hours', 'total_hours', 'progress', 'delay_hours','state'], 10),
- 'project.task.work': (_get_project_work, ['hours'], 10),
- }),
+ 'progress_rate': fields.function(_progress_rate, multi="progress", method=True, string='Progress', type='float', group_operator="avg", help="Percent of tasks closed according to the total of tasks todo."),
'warn_customer': fields.boolean('Warn Partner', help="If you check this, the user will have a popup when closing a task that propose a message to send by email to the customer.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
'warn_header': fields.text('Mail Header', help="Header added at the beginning of the email for the warning message sent to the customer when a task is closed.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
'warn_footer': fields.text('Mail Footer', help="Footer added at the beginning of the email for the warning message sent to the customer when a task is closed.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
if context is None:
context = {}
- proj = self.browse(cr, uid, id, context=context)
default = default or {}
context['active_test'] = False
default['state'] = 'open'
+ proj = self.browse(cr, uid, id, context=context)
if not default.get('name', False):
default['name'] = proj.name + _(' (copy)')
+
res = super(project, self).copy(cr, uid, id, default, context)
+ return res
+
+
+ def template_copy(self, cr, uid, id, default={}, context=None):
+ task_obj = self.pool.get('project.task')
+ proj = self.browse(cr, uid, id, context=context)
+
+ default['tasks'] = [] #avoid to copy all the task automaticly
+ res = self.copy(cr, uid, id, default=default, context=context)
+
+ #copy all the task manually
+ map_task_id = {}
+ for task in proj.tasks:
+ map_task_id[task.id] = task_obj.copy(cr, uid, task.id, {}, context=context)
+
+ self.write(cr, uid, res, {'tasks':[(6,0, map_task_id.values())]})
+ task_obj.duplicate_task(cr, uid, map_task_id, context=context)
return res
def duplicate_template(self, cr, uid, ids, context=None):
if context is None:
context = {}
- project_obj = self.pool.get('project.project')
+ task_pool = self.pool.get('project.task')
data_obj = self.pool.get('ir.model.data')
result = []
for proj in self.browse(cr, uid, ids, context=context):
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')
- new_id = project_obj.copy(cr, uid, proj.id, default = {
+ context.update({'copy':True})
+ new_id = self.template_copy(cr, uid, proj.id, default = {
'name': proj.name +_(' (copy)'),
'state':'open',
'date_start':new_date_start,
def onchange_planned(self, cr, uid, ids, planned = 0.0, effective = 0.0):
return {'value':{'remaining_hours': planned - effective}}
-
+
def onchange_project(self, cr, uid, id, project_id):
if not project_id:
return {}
if partner_id:
return {'value':{'partner_id':partner_id.id}}
return {}
-
+
def _default_project(self, cr, uid, context=None):
if context is None:
context = {}
return int(context['project_id'])
return False
+ def duplicate_task(self, cr, uid, map_ids, context=None):
+ for new in map_ids.values():
+ task = self.browse(cr, uid, new, context)
+ child_ids = [ ch.id for ch in task.child_ids]
+ if task.child_ids:
+ for child in task.child_ids:
+ if child.id in map_ids.keys():
+ child_ids.remove(child.id)
+ child_ids.append(map_ids[child.id])
+
+ parent_ids = [ ch.id for ch in task.parent_ids]
+ if task.parent_ids:
+ for parent in task.parent_ids:
+ if parent.id in map_ids.keys():
+ parent_ids.remove(parent.id)
+ parent_ids.append(map_ids[parent.id])
+ #FIXME why there is already the copy and the old one
+ self.write(cr, uid, new, {'parent_ids':[(6,0,set(parent_ids))], 'child_ids':[(6,0, set(child_ids))]})
+
def copy_data(self, cr, uid, id, default={}, context=None):
default = default or {}
default.update({'work_ids':[], 'date_start': False, 'date_end': False, 'date_deadline': False})
default['active'] = True
default['type_id'] = False
if not default.get('name', False):
- default['name'] = self.browse(cr, uid, id, context=context).name
+ default['name'] = self.browse(cr, uid, id, context=context).name or ''
+ if not context.get('copy',False):
+ new_name = _("%s (copy)")%default.get('name','')
+ default.update({'name':new_name})
return super(task, self).copy_data(cr, uid, id, default, context)
def _check_dates(self, cr, uid, ids, context=None):
'active': fields.function(_is_template, method=True, 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."),
'name': fields.char('Task Summary', size=128, required=True),
'description': fields.text('Description'),
- 'priority': fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Urgent'), ('0','Very urgent')], 'Priority'),
+ 'priority': fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Important'), ('0','Very important')], 'Priority'),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of tasks."),
'type_id': fields.many2one('project.task.type', 'Stage'),
'state': fields.selection([('draft', 'Draft'),('open', 'In Progress'),('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True,
help='If the task is created the state is \'Draft\'.\n If the task is started, the state becomes \'In Progress\'.\n If review is needed the task is in \'Pending\' state.\
\n If the task is over, the states is set to \'Done\'.'),
- 'create_date': fields.datetime('Create Date', readonly=True),
- 'date_start': fields.datetime('Starting Date'),
- 'date_end': fields.datetime('Ending Date'),
- 'date_deadline': fields.date('Deadline'),
+ 'create_date': fields.datetime('Create Date', readonly=True,select=True),
+ 'date_start': fields.datetime('Starting Date',select=True),
+ 'date_end': fields.datetime('Ending Date',select=True),
+ 'date_deadline': fields.date('Deadline',select=True),
'project_id': fields.many2one('project.project', 'Project', ondelete='cascade'),
'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'),
_order = "sequence,priority, date_start, name, id"
def _check_recursion(self, cr, uid, ids, context=None):
- obj_task = self.browse(cr, uid, ids[0], context=context)
- parent_ids = [x.id for x in obj_task.parent_ids]
- children_ids = [x.id for x in obj_task.child_ids]
+ 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
+
+ return True
- if (obj_task.id in children_ids) or (obj_task.id in parent_ids):
+ def _check_cycle(self, cr, uid, id, visited_branch, visited_node, context=None):
+ if id in visited_branch: #Cycle
return False
- while(ids):
- cr.execute('SELECT DISTINCT task_id '\
- 'FROM project_task_parent_rel '\
- 'WHERE parent_id IN %s', (tuple(ids),))
- child_ids = map(lambda x: x[0], cr.fetchall())
- c_ids = child_ids
- if (list(set(parent_ids).intersection(set(c_ids)))) or (obj_task.id in c_ids):
+ 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:
+ return False
+
+ visited_branch.remove(id)
+ return True
+
+ 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 :
+ if start > end:
return False
- while len(c_ids):
- s_ids = self.search(cr, uid, [('parent_ids', 'in', c_ids)])
- if (list(set(parent_ids).intersection(set(s_ids)))):
- return False
- c_ids = s_ids
- ids = child_ids
return True
_constraints = [
- (_check_recursion, 'Error ! You cannot create recursive tasks.', ['parent_ids'])
+ (_check_recursion, 'Error ! You cannot create recursive tasks.', ['parent_ids']),
+ (_check_dates, 'Error ! Task end-date must be greater then task start-date', ['date_start','date_end'])
]
#
# Override view according to the company definition
"""
request = self.pool.get('res.request')
for task in self.browse(cr, uid, ids, context=context):
+ vals = {}
project = task.project_id
if project:
# Send request to project manager
reopen = False
if reopen:
self.do_reopen(cr, uid, [parent_id.id])
- self.write(cr, uid, [task.id], {'state': 'done', 'date_end':time.strftime('%Y-%m-%d %H:%M:%S'), 'remaining_hours': 0.0})
+ vals.update({'state': 'done'})
+ vals.update({'remaining_hours': 0.0})
+ if not task.date_end:
+ vals.update({ 'date_end':time.strftime('%Y-%m-%d %H:%M:%S')})
+ self.write(cr, uid, [task.id],vals)
message = _("The task '%s' is done") % (task.name,)
self.log(cr, uid, task.id, message)
return True