[ADD] project_timesheet, sale_service: support for project sub-tasks

This commit introduces "Sub-task", a concept remotely similar to
the "delegated tasks" available in version 7.0, but covering a
more specific and narrow scope.

Activating Sub-tasks
--------------------

Sub-tasks are made available within any project by configuring
a "sub-task project" in the project settings. This feature is
considered low-level and technical, and only accessible with
"Technical Features" enabled.
The "sub-task project" may be the project itself.

Using Sub-tasks
---------------

Conceptually, a sub-task represents the allocation of a part
of the scope of its parent task. Multiple sub-task levels
are possible.

Once activated in project settings, a new "Sub-tasks" smart button
will appear on the task form, giving the option to view and
create sub-tasks. In addition, a new field "Sub-tasks Hours" will
appear next to the other computed "Hours" fields, representing
the total hours allocated to sub-tasks.
(Technically, the sum of the maximum between the planned hours
and spent hours of all sub-tasks, with spent hours always used
when a task is closed)
These sub-task hours will be dynamically subtracted from the
remaining hours of the task, and will therefore affect the
task's progress as well.

Upon creation, a sub-task will inherit the link to the
original sales order and procurement from its parent task, and
will by default be assigned to the "sub-task project" set on
the project of the parent task.

Enhancement
-----------
The kanban view for tasks will now display a color-coded label
with the number of remaining hours, whenever the percentage
of remaining hours is below 20% (orange=20%-, red=1%-).

Notes
-----------
This is a forward port of 43c42b304d

Since hr_timesheet has been migrated to the new API, the forward port changes mostly consists in migrating the sub-tasks system to the new API as well.
This commit is contained in:
Thibault Francois
2016-03-24 13:44:21 +01:00
committed by Tanguy Charlier
parent 7c49782fea
commit e6ac7a7d25
6 changed files with 108 additions and 7 deletions
+32 -3
View File
@@ -2,6 +2,14 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, fields, api
from openerp.osv import osv
class Project(models.Model):
_inherit = "project.project"
subtask_project_id = fields.Many2one('project.project', string='Sub-task Project', help="Choosing a sub-tasks project will both enable sub-tasks and set their default project (possibly the project itself)",
ondelete="restrict")
class Task(models.Model):
@@ -10,18 +18,31 @@ class Task(models.Model):
def _get_total_hours(self):
return super(Task, self)._get_total_hours() + self.effective_hours
@api.depends('timesheet_ids', 'timesheet_ids.unit_amount', 'planned_hours')
@api.multi
def _get_subtask_count(self):
for task in self:
task.subtask_count = self.search_count([('id', 'child_of', task.id)]) - 1 # Don't count task itself
@api.depends('timesheet_ids.unit_amount', 'planned_hours', 'child_ids.stage_id',
'child_ids.planned_hours', 'child_ids.effective_hours', 'child_ids.children_hours')
def _hours_get(self):
for task in self:
for child_task in task.child_ids:
if child_task.stage_id and child_task.stage_id.fold:
task.children_hours += child_task.effective_hours + child_task.children_hours
else:
task.children_hours += max(child_task.planned_hours, child_task.effective_hours + child_task.children_hours)
task.effective_hours = sum(task.timesheet_ids.mapped('unit_amount'))
task.remaining_hours = task.planned_hours - task.effective_hours
task.remaining_hours = task.planned_hours - task.effective_hours - task.children_hours
task.total_hours = max(task.planned_hours, task.effective_hours)
task.delay_hours = max(-task.remaining_hours, 0.0)
if task.stage_id and task.stage_id.fold:
task.progress = 100.0
elif (task.planned_hours > 0.0 and task.effective_hours):
task.progress = round(min(100.0 * task.effective_hours / task.planned_hours, 99.99), 2)
task.progress = round(min(100.0 * (task.effective_hours + task.children_hours) / task.planned_hours, 99.99), 2)
else:
task.progress = 0.0
@@ -30,4 +51,12 @@ class Task(models.Model):
total_hours = fields.Float(compute='_hours_get', store=True, string='Total', help="Computed as: Time Spent + Remaining Time.")
progress = fields.Float(compute='_hours_get', store=True, string='Progress (%)', 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", default=0.0)
delay_hours = fields.Float(compute='_hours_get', store=True, string='Delay Hours', help="Computed as difference between planned hours by the project manager and the total hours of the task.")
children_hours = fields.Float(compute='_hours_get', store=True, string='Sub-tasks Hours', help="Sum of the planned hours of all sub-tasks (when a sub-task is closed or its spent hours exceed its planned hours, spent hours are counted instead)")
timesheet_ids = fields.One2many('account.analytic.line', 'task_id', 'Timesheets')
parent_id = fields.Many2one('project.task', string='Parent Task')
child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks")
subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project')
subtask_count = fields.Integer(compute='_get_subtask_count', type='integer', string="Sub-task count")
_constraints = [(osv.osv._check_recursion, 'Circular references are not permitted between tasks and sub-tasks', ['parent_id'])]
+54 -1
View File
@@ -28,21 +28,52 @@
<div name="button_box" position="inside">
<button class="oe_stat_button" name="%(act_hr_timesheet_line_by_project)d" type="action" icon="fa-calendar" string="Timesheets"/>
</div>
<field name="user_id" position="after">
<field name="subtask_project_id" groups="base.group_no_one" />
</field>
</field>
</record>
<record id="act_project_task_sub_task" model="ir.actions.act_window">
<field name="name">Sub-tasks</field>
<field name="res_model">project.task</field>
<field name="view_mode">kanban,tree,form,calendar,pivot,graph</field>
<field name="domain">[('id', 'child_of', active_id), ('id', '!=', active_id)]</field>
<field name="context">
{
'default_parent_id' : active_id,
'default_project_id' : project_id,
'default_name' : name + ':',
'default_partner_id' : partner_id,
}
</field>
<field name="search_view_id" ref="project.view_task_search_form"/>
</record>
<record model="ir.ui.view" id="view_task_form2_inherited">
<field name="name">project.task.form.inherited</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2" />
<field name="groups_id" eval="[(4, ref('base.group_user'))]"/>
<field name="arch" type="xml">
<button name="toggle_active" position="after">
<button name="%(act_project_task_sub_task)d" type="action" class="oe_stat_button" icon="fa-tasks"
attrs="{'invisible' : [('subtask_project_id', '=', False)]}"
context="{'project_id': subtask_project_id, 'name': name, 'partner_id': partner_id}" >
<field string="Sub-tasks" name="subtask_count" widget="statinfo"/>
</button>
</button>
<field name="tag_ids" position="after">
<field name="progress" widget="progressbar"/>
</field>
<field name="user_id" position="after">
<field name="planned_hours" widget="float_time"/>
</field>
<field name="partner_id" position="after">
<field name="parent_id" attrs="{'invisible' : [('parent_id', '=', False)]}" />
<field name="child_ids" invisible="1" />
<field name="subtask_project_id" invisible="1" />
</field>
<xpath expr="//notebook/page[@name='description_page']" position="after">
<page string="Timesheets">
<field name="timesheet_ids" context="{'default_project_id': project_id}">
@@ -57,7 +88,8 @@
<group>
<group class="oe_subtotal_footer oe_right" name="project_hours">
<field name="effective_hours" widget="float_time" />
<field name="remaining_hours" widget="float_time" class="oe_subtotal_footer_separator" readonly="1" />
<field name="children_hours" widget="float_time" attrs="{'invisible' : [('children_hours', '=', 0.0)]}" />
<field name="remaining_hours" widget="float_time" class="oe_subtotal_footer_separator" readonly="1"/>
</group>
</group>
</page>
@@ -96,5 +128,26 @@
</field>
</record>
<record id="view_task_kanban_inherited_progress" model="ir.ui.view">
<field name="name">project.task.timesheet.kanban.inherited.progress</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_kanban"/>
<field name="arch" type="xml">
<templates position="before">
<field name="progress" />
<field name="remaining_hours" />
</templates>
<div class="oe_kanban_bottom_left" position="inside">
<t t-if="record.progress.raw_value &gt; 80 and record.progress.raw_value &lt; 100">
<div t-att-class="'oe_kanban_align label label-' + (record.progress.raw_value &gt;= 99 ? 'danger': 'warning')"
title="Remaining hours">
<field name="remaining_hours" widget="float_time" />
</div>
</t>
</div>
</field>
</record>
</data>
</openerp>
+3 -3
View File
@@ -281,14 +281,14 @@
<div class="oe_clear"/>
</page>
<page string="Extra Info">
<group col="4">
<group col="2">
<group>
<group>
<field name="sequence" groups="base.group_no_one"/>
<field name="partner_id"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="displayed_image_id" groups="base.group_no_one"/>
</group>
<group col="2">
<group>
<field name="date_assign" groups="base.group_no_one"/>
<field name="date_last_stage_update" groups="base.group_no_one"/>
</group>
@@ -111,6 +111,17 @@ class project_task(osv.osv):
"context": {"create": False, "show_sale": True},
}
def onchange_parent_id(self, cr, uid, ids, parent_id, context=None):
if not parent_id:
return {'value' : {'procurement_id': False, 'sale_line_id': False }}
parent_task = self.browse(cr, uid, parent_id, context=context)
return {
'value' : {
'procurement_id' : parent_task.procurement_id.id,
'sale_line_id' : parent_task.sale_line_id.id,
}
}
class ProductProduct(models.Model):
_inherit = 'product.product'
@@ -24,8 +24,12 @@
string="Sales Order"/>
</xpath>
<field name="company_id" position="before">
<field name="procurement_id" groups="base.group_no_one"/>
<field name="sale_line_id" string="Order Line" groups="base.group_no_one"/>
</field>
<field name="parent_id" position="attributes">
<attribute name="on_change">onchange_parent_id(parent_id)</attribute>
</field>
</field>
</record>
</data>
@@ -96,6 +96,10 @@
.oe_kanban_bottom_left {
float: left;
.oe_kanban_align {
display: inline-block;
vertical-align: middle;
}
}
.o_kanban_tags {