from openerp import SUPERUSER_ID
from openerp import tools
+from openerp.addons.resource.faces import task as Task
from openerp.osv import fields, osv
from openerp.tools.translate import _
-from openerp.addons.base_status.base_stage import base_stage
-from openerp.addons.resource.faces import task as Task
-
-_TASK_STATE = [('draft', 'New'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done'), ('cancelled', 'Cancelled')]
class project_task_type(osv.osv):
_name = 'project.task.type'
'case_default': fields.boolean('Default for New Projects',
help="If you check this field, this stage will be proposed by default on each new project. It will not assign this stage to existing projects."),
'project_ids': fields.many2many('project.project', 'project_task_type_rel', 'type_id', 'project_id', 'Projects'),
- 'state': fields.selection(_TASK_STATE, 'Related Status', required=True,
- help="The status of your document is automatically changed regarding the selected stage. " \
- "For example, if a stage is related to the status 'Close', when your document reaches this stage, it is automatically closed."),
- 'fold': fields.boolean('Folded by Default',
- help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
+ '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.'),
}
- def _get_default_project_id(self, cr, uid, ctx={}):
- proj = ctx.get('default_project_id', False)
- if type(proj) is int:
- return [proj]
- return proj
+
+ 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
+
_defaults = {
'sequence': 1,
- 'state': 'open',
- 'fold': False,
- 'case_default': False,
- 'project_ids': _get_default_project_id
+ 'project_ids': _get_default_project_ids,
}
_order = 'sequence'
-def short_name(name):
- """Keep first word(s) of name to make it small enough
- but distinctive"""
- if not name: return name
- # keep 7 chars + end of the last word
- keep_words = name[:7].strip().split()
- return ' '.join(name.split()[:len(keep_words)])
class project(osv.osv):
_name = "project.project"
# create aliases for all projects and avoid constraint errors
alias_context = dict(context, alias_model_name='project.task')
return self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(project, self)._auto_init,
- self._columns['alias_id'], 'id', alias_prefix='project+', alias_defaults={'project_id':'id'}, context=alias_context)
+ 'project.task', self._columns['alias_id'], 'id', alias_prefix='project+', alias_defaults={'project_id':'id'}, context=alias_context)
def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
if user == 1:
def onchange_partner_id(self, cr, uid, ids, part=False, context=None):
partner_obj = self.pool.get('res.partner')
- if not part:
- return {'value':{}}
val = {}
+ if not part:
+ return {'value': val}
if 'pricelist_id' in self.fields_get(cr, uid, context=context):
pricelist = partner_obj.read(cr, uid, part, ['property_product_pricelist'], context=context)
pricelist_id = pricelist.get('property_product_pricelist', False) and pricelist.get('property_product_pricelist')[0] or False
cr.execute("""
SELECT project_id, COALESCE(SUM(planned_hours), 0.0),
COALESCE(SUM(total_hours), 0.0), COALESCE(SUM(effective_hours), 0.0)
- FROM project_task WHERE project_id IN %s AND state <> 'cancelled'
+ FROM project_task
+ LEFT JOIN project_task_type ON project_task.stage_id = project_task_type.id
+ WHERE project_task.project_id IN %s AND project_task_type.fold = False
GROUP BY project_id
""", (tuple(child_parent.keys()),))
# aggregate results into res
- res = dict([(id, {'planned_hours':0.0,'total_hours':0.0,'effective_hours':0.0}) for id in ids])
+ res = dict([(id, {'planned_hours':0.0, 'total_hours':0.0, 'effective_hours':0.0}) for id in ids])
for id, planned, total, effective in cr.fetchall():
# add the values specific to id to all parent projects of id in the result
while id:
_('You cannot delete a project containing tasks. You can either delete all the project\'s tasks and then delete the project or simply deactivate the project.'))
elif proj.alias_id:
alias_ids.append(proj.alias_id.id)
- res = super(project, self).unlink(cr, uid, ids, context=context)
+ res = super(project, self).unlink(cr, uid, ids, context=context)
mail_alias.unlink(cr, uid, alias_ids, context=context)
return res
-
+
def _get_attached_docs(self, cr, uid, ids, field_name, arg, context):
res = {}
attachment = self.pool.get('ir.attachment')
task_attachments = attachment.search(cr, uid, [('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)], context=context, count=True)
res[id] = (project_attachments or 0) + (task_attachments or 0)
return res
-
+
def _task_count(self, cr, uid, ids, field_name, arg, context=None):
+ """ :deprecated: this method will be removed with OpenERP v8. Use task_ids
+ fields instead. """
if context is None:
context = {}
res = dict.fromkeys(ids, 0)
return res
def _get_alias_models(self, cr, uid, context=None):
- """Overriden in project_issue to offer more options"""
+ """ Overriden in project_issue to offer more options """
return [('project.task', "Tasks")]
def _get_visibility_selection(self, cr, uid, context=None):
""" Overriden in portal_project to offer more options """
- return [('public', 'All Users'),
- ('employees', 'Employees Only'),
- ('followers', 'Followers Only')]
+ return [('public', 'Public project'),
+ ('employees', 'Internal project: all employees can access'),
+ ('followers', 'Private project: followers Only')]
def attachment_tree_view(self, cr, uid, ids, context):
task_ids = self.pool.get('project.task').search(cr, uid, [('project_id', 'in', ids)])
domain = [
- '|',
+ '|',
'&', ('res_model', '=', 'project.project'), ('res_id', 'in', ids),
- '&', ('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)
- ]
+ '&', ('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)]
res_id = ids and ids[0] or False
return {
'name': _('Attachments'),
'limit': 80,
'context': "{'default_res_model': '%s','default_res_id': %d}" % (self._name, res_id)
}
+
# Lambda indirection method to avoid passing a copy of the overridable method when declaring the field
_alias_models = lambda self, *args, **kwargs: self._get_alias_models(*args, **kwargs)
_visibility_selection = lambda self, *args, **kwargs: self._get_visibility_selection(*args, **kwargs)
'planned_hours': fields.function(_progress_rate, multi="progress", string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent', help="Sum of spent hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'total_hours': fields.function(_progress_rate, multi="progress", string='Total Time', help="Sum of total hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'progress_rate': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg", help="Percent of tasks closed according to the total of tasks todo.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ),
'type_ids': fields.many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', 'Tasks Stages', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
- 'task_count': fields.function(_task_count, type='integer', string="Open Tasks"),
+ 'task_count': fields.function(_task_count, type='integer', string="Open Tasks",
+ deprecated="This field will be removed in OpenERP v8. Use task_ids one2many field instead."),
+ 'task_ids': fields.one2many('project.task', 'project_id',
+ domain=[('stage_id.fold', '=', False)]),
'color': fields.integer('Color Index'),
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True,
help="Internal email associated with this project. Incoming emails are automatically synchronized"
"with Tasks (or optionally Issues if the Issue Tracker module is installed)."),
'alias_model': fields.selection(_alias_models, "Alias Model", select=True, required=True,
help="The kind of document created when an email is received on this project's email alias"),
- 'privacy_visibility': fields.selection(_visibility_selection, 'Privacy / Visibility', required=True),
+ '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."),
'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','Pending'),('close','Closed')], 'Status', required=True,),
'doc_count':fields.function(_get_attached_docs, string="Number of documents attached", type='int')
}
'type_ids': _get_type_common,
'alias_model': 'project.task',
'privacy_visibility': 'employees',
- 'alias_domain': False, # always hide alias during creation
}
# TODO: Why not using a SQL contraints ?
]
def set_template(self, cr, uid, ids, context=None):
- res = self.setActive(cr, uid, ids, value=False, context=context)
- return res
+ return self.setActive(cr, uid, ids, value=False, context=context)
def set_done(self, cr, uid, ids, context=None):
- task_obj = self.pool.get('project.task')
- task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', 'not in', ('cancelled', 'done'))])
- task_obj.case_close(cr, uid, task_ids, context=context)
- return self.write(cr, uid, ids, {'state':'close'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'close'}, context=context)
def set_cancel(self, cr, uid, ids, context=None):
- task_obj = self.pool.get('project.task')
- task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', '!=', 'done')])
- task_obj.case_cancel(cr, uid, task_ids, context=context)
- return self.write(cr, uid, ids, {'state':'cancelled'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
def set_pending(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state':'pending'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'pending'}, context=context)
def set_open(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state':'open'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'open'}, context=context)
def reset_project(self, cr, uid, ids, context=None):
return self.setActive(cr, uid, ids, value=True, context=context)
# they are discarded in analytic.copy(), and handled in duplicate_template()
default['child_ids'] = []
- default.pop('alias_name', None)
- default.pop('alias_id', None)
proj = self.browse(cr, uid, id, context=context)
if not default.get('name', False):
default.update(name=_("%s (copy)") % (proj.name))
res = super(project, self).copy(cr, uid, id, default, context)
- self.map_tasks(cr,uid,id,res,context)
+ self.map_tasks(cr, uid, id, res, context=context)
return res
def duplicate_template(self, cr, uid, ids, context=None):
if project.user_id and (project.user_id.id not in u_ids):
u_ids.append(project.user_id.id)
for task in project.tasks:
- if task.state in ('done','cancelled'):
- continue
if task.user_id and (task.user_id.id not in u_ids):
u_ids.append(task.user_id.id)
calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
for project in projects:
project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,))
for task in project.tasks:
- if task.state in ('done','cancelled'):
+ if task.stage_id and task.stage_id.fold:
continue
p = getattr(project_gantt, 'Task_%d' % (task.id,))
}, context=context)
return True
- # ------------------------------------------------
- # OpenChatter methods and notifications
- # ------------------------------------------------
-
def create(self, cr, uid, vals, context=None):
- if context is None: context = {}
- # Prevent double project creation when 'use_tasks' is checked!
- context = dict(context, project_creation_in_progress=True)
- mail_alias = self.pool.get('mail.alias')
- if not vals.get('alias_id') and vals.get('name', False):
- vals.pop('alias_name', None) # prevent errors during copy()
- alias_id = mail_alias.create_unique_alias(cr, uid,
- # Using '+' allows using subaddressing for those who don't
- # have a catchall domain setup.
- {'alias_name': "project+"+short_name(vals['name'])},
- model_name=vals.get('alias_model', 'project.task'),
- context=context)
- vals['alias_id'] = alias_id
- if vals.get('type', False) not in ('template','contract'):
+ if context is None:
+ context = {}
+ # Prevent double project creation when 'use_tasks' is checked + alias management
+ create_context = dict(context, project_creation_in_progress=True,
+ alias_model_name=vals.get('alias_model', 'project.task'),
+ alias_parent_model_name=self._name)
+
+ if vals.get('type', False) not in ('template', 'contract'):
vals['type'] = 'contract'
- project_id = super(project, self).create(cr, uid, vals, context)
- mail_alias.write(cr, uid, [vals['alias_id']], {'alias_defaults': {'project_id': project_id} }, context)
+
+ project_id = super(project, self).create(cr, uid, vals, context=create_context)
+ project_rec = self.browse(cr, uid, project_id, context=context)
+ self.pool.get('mail.alias').write(cr, uid, [project_rec.alias_id.id], {'alias_parent_thread_id': project_id, 'alias_defaults': {'project_id': project_id}}, context)
return project_id
def write(self, cr, uid, ids, vals, context=None):
vals.update(alias_model_id=model_ids[0])
return super(project, self).write(cr, uid, ids, vals, context=context)
-class task(base_stage, osv.osv):
+
+class task(osv.osv):
_name = "project.task"
_description = "Task"
_date_name = "date_start"
_inherit = ['mail.thread', 'ir.needaction_mixin']
+ _mail_post_access = 'read'
_track = {
- 'state': {
- 'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj['state'] in ['new', 'draft'],
- 'project.mt_task_started': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'open',
- 'project.mt_task_closed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
- },
'stage_id': {
- 'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj['state'] not in ['new', 'draft', 'done', 'open'],
+ # 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,
+ },
+ 'user_id': {
+ 'project.mt_task_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id,
},
- 'kanban_state': { # kanban state: tracked, but only block subtype
- 'project.mt_task_blocked': lambda self, cr, uid, obj, ctx=None: obj['kanban_state'] == 'blocked',
+ 'kanban_state': {
+ 'project.mt_task_blocked': lambda self, cr, uid, obj, ctx=None: obj.kanban_state == 'blocked',
},
}
def _get_default_partner(self, cr, uid, context=None):
- """ Override of base_stage to add project specific behavior """
project_id = self._get_default_project_id(cr, uid, context)
if project_id:
project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
if project and project.partner_id:
return project.partner_id.id
- return super(task, self)._get_default_partner(cr, uid, context=context)
+ return False
def _get_default_project_id(self, cr, uid, context=None):
""" Gives default section by checking if present in the context """
def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """
project_id = self._get_default_project_id(cr, uid, context=context)
- return self.stage_find(cr, uid, [], project_id, [('state', '=', 'draft')], context=context)
+ return self.stage_find(cr, uid, [], project_id, [('fold', '=', False)], context=context)
def _resolve_project_id_from_context(self, cr, uid, context=None):
""" Returns ID of project based on the value of 'default_project_id'
res[task.id]['progress'] = 0.0
if (task.remaining_hours + hours.get(task.id, 0.0)):
res[task.id]['progress'] = round(min(100.0 * hours.get(task.id, 0.0) / res[task.id]['total_hours'], 99.99),2)
- if task.state in ('done','cancelled'):
+ # TDE CHECK: if task.state in ('done','cancelled'):
+ if task.stage_id and task.stage_id.fold:
res[task.id]['progress'] = 100.0
return res
def onchange_remaining(self, cr, uid, ids, remaining=0.0, planned=0.0):
if remaining and not planned:
- return {'value':{'planned_hours': remaining}}
+ return {'value': {'planned_hours': remaining}}
return {}
def onchange_planned(self, cr, uid, ids, planned=0.0, effective=0.0):
- return {'value':{'remaining_hours': planned - effective}}
+ return {'value': {'remaining_hours': planned - effective}}
def onchange_project(self, cr, uid, id, project_id, context=None):
if project_id:
return {'value': {'partner_id': project.partner_id.id}}
return {}
+ def onchange_user_id(self, cr, uid, ids, user_id, context=None):
+ vals = {}
+ if user_id:
+ vals['date_start'] = fields.datetime.now()
+ return {'value': vals}
+
def duplicate_task(self, cr, uid, map_ids, context=None):
mapper = lambda t: map_ids.get(t.id, t.id)
for task in self.browse(cr, uid, map_ids.values(), context):
'priority': fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Important'), ('0','Very important')], 'Priority', select=True),
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of tasks."),
'stage_id': fields.many2one('project.task.type', 'Stage', track_visibility='onchange',
- domain="['&', ('fold', '=', False), ('project_ids', '=', project_id)]"),
- 'state': fields.related('stage_id', 'state', type="selection", store=True,
- selection=_TASK_STATE, string="Status", readonly=True,
- help='The status is set to \'Draft\', when a case is created.\
- If the case is in progress the status is set to \'Open\'.\
- When the case is over, the status is set to \'Done\'.\
- If the case needs to be reviewed then the status is \
- set to \'Pending\'.'),
+ domain="[('project_ids', '=', project_id)]"),
'categ_ids': fields.many2many('project.category', string='Tags'),
'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
track_visibility='onchange',
'date_start': fields.datetime('Starting Date',select=True),
'date_end': fields.datetime('Ending Date',select=True),
'date_deadline': fields.date('Deadline',select=True),
+ 'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
'project_id': fields.many2one('project.project', 'Project', ondelete='set null', select="1", track_visibility='onchange'),
'parent_ids': fields.many2many('project.task', 'project_task_parent_rel', 'task_id', 'parent_id', 'Parent Tasks'),
'child_ids': fields.many2many('project.task', 'project_task_parent_rel', 'parent_id', 'task_id', 'Delegated Tasks'),
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
- 'progress': fields.function(_hours_get, string='Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
+ 'progress': fields.function(_hours_get, string='Working Time Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours', 'state', 'stage_id'], 10),
'project.task.work': (_get_task, ['hours'], 10),
_defaults = {
'stage_id': _get_default_stage_id,
'project_id': _get_default_project_id,
+ 'date_last_stage_update': fields.datetime.now,
'kanban_state': 'normal',
'priority': '2',
'progress': 0,
res['fields'][f]['string'] = res['fields'][f]['string'].replace('Hours',tm)
return res
+ def get_empty_list_help(self, cr, uid, help, context=None):
+ context['empty_list_help_id'] = context.get('default_project_id')
+ context['empty_list_help_model'] = 'project.project'
+ context['empty_list_help_document_name'] = _("tasks")
+ return super(task, self).get_empty_list_help(cr, uid, help, context=context)
+
# ----------------------------------------
# Case management
# ----------------------------------------
section_ids.append(task.project_id.id)
search_domain = []
if section_ids:
- search_domain = [('|')] * (len(section_ids)-1)
+ search_domain = [('|')] * (len(section_ids) - 1)
for section_id in section_ids:
search_domain.append(('project_ids', '=', section_id))
search_domain += list(domain)
for task in tasks:
if task.child_ids:
for child in task.child_ids:
- if child.state in ['draft', 'open', 'pending']:
+ if child.stage_id and not child.stage_id.fold:
raise osv.except_osv(_("Warning!"), _("Child task still open.\nPlease cancel or complete child task first."))
return True
- def action_close(self, cr, uid, ids, context=None):
- """ This action closes the task
- """
- task_id = len(ids) and ids[0] or False
- self._check_child_task(cr, uid, ids, context=context)
- if not task_id: return False
- return self.do_close(cr, uid, [task_id], context=context)
-
- def do_close(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_close. """
- return self.case_close(cr, uid, ids, context=context)
-
- def case_close(self, cr, uid, ids, context=None):
- """ Closes Task """
- if not isinstance(ids, list): ids = [ids]
- for task in self.browse(cr, uid, ids, context=context):
- vals = {}
- project = task.project_id
- for parent_id in task.parent_ids:
- if parent_id.state in ('pending','draft'):
- reopen = True
- for child in parent_id.child_ids:
- if child.id != task.id and child.state not in ('done','cancelled'):
- reopen = False
- if reopen:
- self.do_reopen(cr, uid, [parent_id.id], context=context)
- # close task
- vals['remaining_hours'] = 0.0
- if not task.date_end:
- vals['date_end'] = fields.datetime.now()
- self.case_set(cr, uid, [task.id], 'done', vals, context=context)
- return True
-
- def do_reopen(self, cr, uid, ids, context=None):
- for task in self.browse(cr, uid, ids, context=context):
- project = task.project_id
- self.case_set(cr, uid, [task.id], 'open', {}, context=context)
- return True
-
- def do_cancel(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_cancel. """
- return self.case_cancel(cr, uid, ids, context=context)
-
- def case_cancel(self, cr, uid, ids, context=None):
- tasks = self.browse(cr, uid, ids, context=context)
- self._check_child_task(cr, uid, ids, context=context)
- for task in tasks:
- self.case_set(cr, uid, [task.id], 'cancelled', {'remaining_hours': 0.0}, context=context)
- return True
-
- def do_open(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_open. """
- return self.case_open(cr, uid, ids, context=context)
-
- def case_open(self, cr, uid, ids, context=None):
- if not isinstance(ids,list): ids = [ids]
- return self.case_set(cr, uid, ids, 'open', {'date_start': fields.datetime.now()}, context=context)
-
- def do_draft(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_draft. """
- return self.case_draft(cr, uid, ids, context=context)
-
- def case_draft(self, cr, uid, ids, context=None):
- return self.case_set(cr, uid, ids, 'draft', {}, context=context)
-
- def do_pending(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_pending. """
- return self.case_pending(cr, uid, ids, context=context)
-
- def case_pending(self, cr, uid, ids, context=None):
- return self.case_set(cr, uid, ids, 'pending', {}, context=context)
-
def _delegate_task_attachments(self, cr, uid, task_id, delegated_task_id, context=None):
attachment = self.pool.get('ir.attachment')
attachment_ids = attachment.search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', task_id)], context=context)
delegated_task_id = self.copy(cr, uid, task.id, {
'name': delegate_data['name'],
'project_id': delegate_data['project_id'] and delegate_data['project_id'][0] or False,
+ 'stage_id': delegate_data.get('stage_id') and delegate_data.get('stage_id')[0] or False,
'user_id': delegate_data['user_id'] and delegate_data['user_id'][0] or False,
'planned_hours': delegate_data['planned_hours'] or 0.0,
'parent_ids': [(6, 0, [task.id])],
'planned_hours': delegate_data['planned_hours_me'] + (task.effective_hours or 0.0),
'name': newname,
}, context=context)
- if delegate_data['state'] == 'pending':
- self.do_pending(cr, uid, [task.id], context=context)
- elif delegate_data['state'] == 'done':
- self.do_close(cr, uid, [task.id], context=context)
delegated_tasks[task.id] = delegated_task_id
return delegated_tasks
def set_remaining_time(self, cr, uid, ids, remaining_time=1.0, context=None):
for task in self.browse(cr, uid, ids, context=context):
- if (task.state=='draft') or (task.planned_hours==0.0):
+ if (task.stage_id and task.stage_id.sequence <= 1) or (task.planned_hours == 0.0):
self.write(cr, uid, [task.id], {'planned_hours': remaining_time}, context=context)
self.write(cr, uid, ids, {'remaining_hours': remaining_time}, context=context)
return True
'planned_hours': task.planned_hours,
'kanban_state': task.kanban_state,
'type_id': task.stage_id.id,
- 'state': task.state,
'user_id': task.user_id.id
}, context=context)
return True
+ # ------------------------------------------------
+ # CRUD overrides
+ # ------------------------------------------------
+
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
+
+ # for default stage
if vals.get('project_id') and not context.get('default_project_id'):
context['default_project_id'] = vals.get('project_id')
+ # user_id change: update date_start
+ if vals.get('user_id'):
+ vals['date_start'] = fields.datetime.now()
# context: no_log, because subtype already handle this
create_context = dict(context, mail_create_nolog=True)
self._store_history(cr, uid, [task_id], context=context)
return task_id
- # Overridden to reset the kanban_state to normal whenever
- # the stage (stage_id) of the task changes.
def write(self, cr, uid, ids, vals, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
+
+ # stage change: update date_last_stage_update
+ if 'stage_id' in vals:
+ vals['date_last_stage_update'] = fields.datetime.now()
+ # user_id change: update date_start
+ if vals.get('user_id'):
+ vals['date_start'] = fields.datetime.now()
+
+ # Overridden to reset the kanban_state to normal whenever
+ # the stage (stage_id) of the task changes.
if vals and not 'kanban_state' in vals and 'stage_id' in vals:
new_stage = vals.get('stage_id')
vals_reset_kstate = dict(vals, kanban_state='normal')
for t in self.browse(cr, uid, ids, context=context):
- #TO FIX:Kanban view doesn't raise warning
- #stages = [stage.id for stage in t.project_id.type_ids]
- #if new_stage not in stages:
- #raise osv.except_osv(_('Warning!'), _('Stage is not defined in the project.'))
write_vals = vals_reset_kstate if t.stage_id != new_stage else vals
super(task, self).write(cr, uid, [t.id], write_vals, context=context)
result = True
else:
result = super(task, self).write(cr, uid, ids, vals, context=context)
- if ('stage_id' in vals) or ('remaining_hours' in vals) or ('user_id' in vals) or ('state' in vals) or ('kanban_state' in vals):
+
+ if any(item in vals for item in ['stage_id', 'remaining_hours', 'user_id', 'kanban_state']):
self._store_history(cr, uid, ids, context=context)
return result
result = ""
ident = ' '*ident
for task in tasks:
- if task.state in ('done','cancelled'):
+ if task.stage_id and task.stage_id.fold:
continue
result += '''
%sdef Task_%s():
pass
return super(task, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
- def project_task_reevaluate(self, cr, uid, ids, context=None):
- if self.pool.get('res.users').has_group(cr, uid, 'project.group_time_work_estimation_tasks'):
- return {
- 'view_type': 'form',
- "view_mode": 'form',
- 'res_model': 'project.task.reevaluate',
- 'type': 'ir.actions.act_window',
- 'target': 'new',
- }
- return self.do_reopen(cr, uid, ids, context=context)
-
class project_work(osv.osv):
_name = "project.task.work"
_description = "Project Task Work"
def _get_date(self, cr, uid, ids, name, arg, context=None):
result = {}
for history in self.browse(cr, uid, ids, context=context):
- if history.state in ('done','cancelled'):
+ if history.type_id and history.type_id.fold:
result[history.id] = history.date
continue
cr.execute('''select
_columns = {
'task_id': fields.many2one('project.task', 'Task', ondelete='cascade', required=True, select=True),
'type_id': fields.many2one('project.task.type', 'Stage'),
- 'state': fields.selection([('draft', 'New'), ('cancelled', 'Cancelled'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done')], 'Status'),
- 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State', required=False),
+ 'kanban_state': fields.selection([('normal', 'Normal'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')], 'Kanban State', required=False),
'date': fields.date('Date', select=True),
'end_date': fields.function(_get_date, string='End Date', type="date", store={
'project.task.history': (_get_related_date, None, 20)
}),
- 'remaining_hours': fields.float('Remaining Time', digits=(16,2)),
- 'planned_hours': fields.float('Planned Time', digits=(16,2)),
+ 'remaining_hours': fields.float('Remaining Time', digits=(16, 2)),
+ 'planned_hours': fields.float('Planned Time', digits=(16, 2)),
'user_id': fields.many2one('res.users', 'Responsible'),
}
_defaults = {
SELECT
h.id AS history_id,
h.date+generate_series(0, CAST((coalesce(h.end_date, DATE 'tomorrow')::date - h.date) AS integer)-1) AS date,
- h.task_id, h.type_id, h.user_id, h.kanban_state, h.state,
+ h.task_id, h.type_id, h.user_id, h.kanban_state,
greatest(h.remaining_hours, 1) AS remaining_hours, greatest(h.planned_hours, 1) AS planned_hours,
t.project_id
FROM