[IMP] project, *_timesheet: move subtasks to project
-> Move Subtasks mechanism from hr_timesheet to project.
-> Various functional improvements:
- Add new 'Sub-tasks' setting and 'Sub-task Project' field based on
this setting.
- Relabel 'Team Collaboration' section to 'Task Management'.
- Remove 'Customer Satisfaction' section and move setting
'Rating on Tasks' into 'Task Management' section.
- Add customer name on the project's kanban view.
- Set default value of 'Sub-task Project' field is the same project.
- Add 'Parent Task' stat button on task form view.
- Make 'Sub-Tasks' stat button visible when no parent task
This commit is contained in:
committed by
Denis Vermylen
parent
7d99811a0f
commit
332b83ef9e
@@ -8,19 +8,11 @@ from odoo.exceptions import UserError
|
||||
class Project(models.Model):
|
||||
_inherit = "project.project"
|
||||
|
||||
subtask_project_id = fields.Many2one(
|
||||
'project.project', string='Sub-task Project', ondelete="restrict",
|
||||
help="Choosing a sub-tasks project will both enable sub-tasks and set their default project (possibly the project itself)")
|
||||
allow_timesheets = fields.Boolean("Allow timesheets", default=True)
|
||||
|
||||
class Task(models.Model):
|
||||
_inherit = "project.task"
|
||||
|
||||
@api.multi
|
||||
def _get_subtask_count(self):
|
||||
for task in self:
|
||||
task.subtask_count = self.search_count([('id', 'child_of', task.id), ('id', '!=', task.id)])
|
||||
|
||||
@api.depends('stage_id', 'timesheet_ids.unit_amount', 'planned_hours', 'child_ids.stage_id',
|
||||
'child_ids.planned_hours', 'child_ids.effective_hours', 'child_ids.children_hours', 'child_ids.timesheet_ids.unit_amount')
|
||||
def _hours_get(self):
|
||||
@@ -55,15 +47,4 @@ class Task(models.Model):
|
||||
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', readonly=True)
|
||||
subtask_count = fields.Integer(compute='_get_subtask_count', type='integer', string="Sub-task count")
|
||||
|
||||
_constraints = [(models.BaseModel._check_recursion, 'Circular references are not permitted between tasks and sub-tasks', ['parent_id'])]
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_subtask_project(self):
|
||||
for task in self:
|
||||
if task.parent_id and task.parent_id.project_id and task.project_id and task.project_id != task.parent_id.project_id.subtask_project_id:
|
||||
raise UserError(_("You can't define a parent task if its project is not correctly configured. The sub-task's project of the parent task's project should be this task's project"))
|
||||
|
||||
@@ -45,9 +45,6 @@
|
||||
<button name="toggle_active" position="before">
|
||||
<button class="oe_stat_button" name="%(act_hr_timesheet_line_by_project)d" type="action" icon="fa-calendar" string="Timesheets" attrs="{'invisible': [('allow_timesheets', '=', False)]}"/>
|
||||
</button>
|
||||
<field name="user_id" position="after">
|
||||
<field name="subtask_project_id" groups="base.group_no_one" attrs="{'invisible': [('allow_timesheets', '=', False)]}"/>
|
||||
</field>
|
||||
<xpath expr="//div[@name='options_active']" position="inside">
|
||||
<div>
|
||||
<field name="allow_timesheets" class="oe_inline" string="Allow timesheets"/>
|
||||
@@ -57,46 +54,18 @@
|
||||
</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="before">
|
||||
<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" groups="base.group_no_one"/>
|
||||
<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" attrs="{'invisible': [('project_id', '=', False)]}">
|
||||
<field name="timesheet_ids" context="{'default_project_id': project_id}">
|
||||
|
||||
@@ -219,6 +219,8 @@ class Project(models.Model):
|
||||
doc_count = fields.Integer(compute='_compute_attached_docs_count', string="Number of documents attached")
|
||||
date_start = fields.Date(string='Start Date')
|
||||
date = fields.Date(string='Expiration Date', index=True, track_visibility='onchange')
|
||||
subtask_project_id = fields.Many2one('project.project', string='Sub-task Project', ondelete="restrict",
|
||||
help="Choosing a sub-tasks project will both enable sub-tasks and set their default project (possibly the project itself)")
|
||||
|
||||
_sql_constraints = [
|
||||
('project_date_greater', 'check(date >= date_start)', 'Error! project start-date must be lower than project end-date.')
|
||||
@@ -256,6 +258,8 @@ class Project(models.Model):
|
||||
# Prevent double project creation when 'use_tasks' is checked
|
||||
self = self.with_context(project_creation_in_progress=True, mail_create_nosubscribe=True)
|
||||
project = super(Project, self).create(vals)
|
||||
if not vals.get('subtask_project_id'):
|
||||
project.subtask_project_id = project.id
|
||||
if project.privacy_visibility == 'portal' and project.partner_id:
|
||||
project.message_subscribe(project.partner_id.ids)
|
||||
return project
|
||||
@@ -411,6 +415,10 @@ class Task(models.Model):
|
||||
legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked Explanation', readonly=True)
|
||||
legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', readonly=True)
|
||||
legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True)
|
||||
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', readonly=True)
|
||||
subtask_count = fields.Integer(compute='_compute_subtask_count', type='integer', string="Sub-task count")
|
||||
|
||||
@api.onchange('project_id')
|
||||
def _onchange_project(self):
|
||||
@@ -438,6 +446,17 @@ class Task(models.Model):
|
||||
default['remaining_hours'] = self.planned_hours
|
||||
return super(Task, self).copy(default)
|
||||
|
||||
@api.multi
|
||||
def _compute_subtask_count(self):
|
||||
for task in self:
|
||||
task.subtask_count = self.search_count([('id', 'child_of', task.id), ('id', '!=', task.id)])
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_subtask_project(self):
|
||||
for task in self:
|
||||
if task.parent_id.project_id and task.project_id != task.parent_id.project_id.subtask_project_id:
|
||||
raise UserError(_("You can't define a parent task if its project is not correctly configured. The sub-task's project of the parent task's project should be this task's project"))
|
||||
|
||||
@api.constrains('date_start', 'date_end')
|
||||
def _check_dates(self):
|
||||
if any(self.filtered(lambda task: task.date_start and task.date_end and task.date_start > task.date_end)):
|
||||
@@ -685,6 +704,16 @@ class Task(models.Model):
|
||||
res['headers'] = repr(headers)
|
||||
return res
|
||||
|
||||
def action_open_parent_task(self):
|
||||
return {
|
||||
'name': _('Parent Task'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'project.task',
|
||||
'res_id': self.parent_id.id,
|
||||
'type': 'ir.actions.act_window'
|
||||
}
|
||||
|
||||
|
||||
class AccountAnalyticAccount(models.Model):
|
||||
_inherit = 'account.analytic.account'
|
||||
|
||||
@@ -25,6 +25,7 @@ class ProjectConfiguration(models.TransientModel):
|
||||
module_sale_timesheet = fields.Boolean("Time Billing")
|
||||
module_hr_expense = fields.Boolean("Expenses")
|
||||
module_project_issue = fields.Boolean("Issue Tracking")
|
||||
group_subtask_project = fields.Boolean("Sub-tasks", implied_group="project.group_subtask_project")
|
||||
|
||||
@api.multi
|
||||
def set_default_generate_project_alias(self):
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
<field name="category_id" ref="base.module_category_hidden"/>
|
||||
</record>
|
||||
|
||||
<record id="group_subtask_project" model="res.groups">
|
||||
<field name="name">Use Subtask Project</field>
|
||||
<field name="category_id" ref="base.module_category_hidden"/>
|
||||
</record>
|
||||
|
||||
<data noupdate="1">
|
||||
<record id="base.default_user" model="res.users">
|
||||
<field name="groups_id" eval="[(4,ref('project.group_project_manager'))]"/>
|
||||
|
||||
@@ -80,6 +80,23 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_task_action_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,
|
||||
'search_default_project_id': [project_id],
|
||||
}
|
||||
</field>
|
||||
<field name="search_view_id" ref="project.view_task_search_form"/>
|
||||
</record>
|
||||
|
||||
<!-- Project -->
|
||||
<record id="edit_project" model="ir.ui.view">
|
||||
<field name="name">project.project.form</field>
|
||||
@@ -121,6 +138,7 @@
|
||||
<group>
|
||||
<field name="user_id" string="Project Manager"
|
||||
attrs="{'readonly':[('active','=',False)]}"/>
|
||||
<field name="subtask_project_id" groups="project.group_subtask_project"/>
|
||||
<field name="privacy_visibility" widget="radio"/>
|
||||
<field name="partner_id" string="Customer"/>
|
||||
</group>
|
||||
@@ -239,6 +257,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="oe_background_grey o_kanban_dashboard o_project_kanban" on_create="project.open_create_project">
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="use_tasks"/>
|
||||
<field name="color"/>
|
||||
<field name="task_count"/>
|
||||
@@ -256,6 +275,9 @@
|
||||
<div class="o_kanban_primary_left">
|
||||
<div class="o_primary">
|
||||
<span><t t-esc="record.name.value"/></span>
|
||||
<span t-if="record.partner_id.value">
|
||||
<strong><t t-esc="record.partner_id.value"/></strong>
|
||||
</span>
|
||||
</div>
|
||||
<div t-if="record.alias_name.value and record.alias_domain.value">
|
||||
<span><i class="fa fa-envelope"></i> <t t-esc="record.alias_id.value"/></span>
|
||||
@@ -353,6 +375,12 @@ action = model.activate_sample_project()
|
||||
</header>
|
||||
<sheet string="Task">
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" icon="fa-tasks" type="object" name="action_open_parent_task" string="Parent Task" attrs="{'invisible' : [('parent_id', '=', False)]}" groups="project.group_subtask_project"/>
|
||||
<button name="%(project_task_action_sub_task)d" type="action" class="oe_stat_button" icon="fa-tasks"
|
||||
attrs="{'invisible' : [('parent_id', '!=', False)]}"
|
||||
context="{'project_id': subtask_project_id, 'name': name, 'partner_id': partner_id}" groups="project.group_subtask_project">
|
||||
<field string="Sub-tasks" name="subtask_count" widget="statinfo"/>
|
||||
</button>
|
||||
<button name="toggle_active" type="object" groups="base.group_user"
|
||||
class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button"
|
||||
@@ -391,6 +419,9 @@ action = model.activate_sample_project()
|
||||
<group>
|
||||
<field name="sequence" groups="base.group_no_one"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="parent_id" groups="project.group_subtask_project"/>
|
||||
<field name="child_ids" invisible="1" />
|
||||
<field name="subtask_project_id" invisible="1" />
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
<field name="displayed_image_id" groups="base.group_no_one"/>
|
||||
</group>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<h2>Team Collaboration</h2>
|
||||
<h2>Task Management</h2>
|
||||
<div class="row mt16 o_settings_container" title="This replaces task's description text field by a collaborative rich text pad. This is advised if tasks involve several people.">
|
||||
<div id="use_collaborative_pad" class="col-xs-12 col-md-6 o_setting_box" title="Lets the company customize which Pad installation should be used to link to new pads (for example: http://ietherpad.com/).">
|
||||
<div class="o_setting_left_pane">
|
||||
@@ -20,7 +20,34 @@
|
||||
<div class="o_setting_right_pane" name="pad_project_right_pane">
|
||||
<label for="module_pad"/>
|
||||
<div class="text-muted">
|
||||
Use collaborative rich text pads in tasks
|
||||
Use collaborative rich text pads on tasks
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="group_subtask_project"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="group_subtask_project"/>
|
||||
<div class="text-muted">
|
||||
Split your tasks to organize your work into sub-milestones
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_rating_project"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_rating_project"/>
|
||||
<div class="text-muted">
|
||||
Track customer satisfaction on tasks
|
||||
</div>
|
||||
<div class="content-group" id="msg_module_rating_project">
|
||||
<div class="text-warning mt16" attrs="{'invisible': [('module_rating_project','=',False)]}">
|
||||
Save this page and come back here to set up the feature.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,25 +159,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Customer Satisfaction</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_rating_project"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_rating_project"/>
|
||||
<div class="text-muted">
|
||||
Track customer satisfaction on tasks
|
||||
</div>
|
||||
<div class="content-group" id="msg_module_rating_project">
|
||||
<div class="text-warning mt16" attrs="{'invisible': [('module_rating_project','=',False)]}">
|
||||
Save this page and come back here to set up the feature.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Issue Management</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" id="project_issue">
|
||||
|
||||
@@ -8,7 +8,7 @@ class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
track_service = fields.Selection(selection_add=[
|
||||
('timesheet', 'Timesheets on project'),
|
||||
('timesheet', 'Timesheets on project (one fare per SO/Project)'),
|
||||
('task', 'Create a task per order line to track hours')])
|
||||
project_id = fields.Many2one(
|
||||
'project.project', 'Project', company_dependent=True,
|
||||
|
||||
Reference in New Issue
Block a user