[IMP] project: improve ux

Before this committ:
- Stats buttons were hidden when records are zero
- Some wordings were insignificant
- Existing filters don't allow us to check blocked, blocking and tasks soon in overtime and some other information
- Product form fields need some changes in their order

After this commit:
- All stats button are displayed when records are zero
- Wordings fixed
- New filters added to check blocked, blocking tasks and tasks in overtime soon
- Fields were rearanged

task-2726465

closes odoo/odoo#82253

Related: odoo/enterprise#23257
Related: odoo/upgrade#3185
Signed-off-by: Laurent Stukkens (ltu) <ltu@odoo.com>
This commit is contained in:
Raouf
2022-06-02 18:58:14 +02:00
parent ca200e2ba5
commit 83760b9f10
18 changed files with 119 additions and 115 deletions
+22 -1
View File
@@ -5,7 +5,7 @@ from collections import defaultdict
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError, RedirectWarning
from odoo.addons.rating.models.rating_data import OPERATOR_MAPPING
PROJECT_TASK_READABLE_FIELDS = {
'allow_subtasks',
@@ -252,6 +252,7 @@ class Task(models.Model):
analytic_account_active = fields.Boolean("Active Analytic Account", compute='_compute_analytic_account_active', compute_sudo=True)
allow_timesheets = fields.Boolean("Allow timesheets", related='project_id.allow_timesheets', help="Timesheets can be logged on this task.", readonly=True)
remaining_hours = fields.Float("Remaining Hours", compute='_compute_remaining_hours', store=True, readonly=True, help="Total remaining time, can be re-estimated periodically by the assignee of the task.")
remaining_hours_percentage = fields.Float(compute='_compute_remaining_hours_percentage', search='_search_remaining_hours_percentage')
effective_hours = fields.Float("Hours Spent", compute='_compute_effective_hours', compute_sudo=True, store=True, help="Time spent on this task, excluding its sub-tasks.")
total_hours_spent = fields.Float("Total Hours", compute='_compute_total_hours_spent', store=True, help="Time spent on this task, including its sub-tasks.")
progress = fields.Float("Progress", compute='_compute_progress_hours', store=True, group_operator="avg", help="Display progress of current task.")
@@ -301,6 +302,26 @@ class Task(models.Model):
task.progress = 0.0
task.overtime = 0
@api.depends('planned_hours', 'remaining_hours')
def _compute_remaining_hours_percentage(self):
for task in self:
if task.planned_hours > 0.0:
task.remaining_hours_percentage = task.remaining_hours / task.planned_hours
else:
task.remaining_hours_percentage = 0.0
def _search_remaining_hours_percentage(self, operator, value):
if operator not in OPERATOR_MAPPING:
raise NotImplementedError('This operator %s is not supported in this search method.' % operator)
query = f"""
SELECT id
FROM {self._table}
WHERE remaining_hours > 0
AND planned_hours > 0
AND remaining_hours / planned_hours {operator} %s
"""
return [('id', 'inselect', (query, (value,)))]
@api.depends('effective_hours', 'subtask_effective_hours', 'planned_hours')
def _compute_remaining_hours(self):
for task in self:
+4 -3
View File
@@ -111,12 +111,12 @@
<field name="inherit_id" ref="project.view_task_form2" />
<field name="groups_id" eval="[(6,0, (ref('hr_timesheet.group_hr_timesheet_user'),))]"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='child_ids']/tree/field[@name='kanban_state']" position="after">
<xpath expr="//field[@name='child_ids']/tree/field[@name='company_id']" position="after">
<field name="planned_hours" widget="timesheet_uom_no_toggle" sum="Initially Planned Hours" optional="hide"/>
<field name="effective_hours" widget="timesheet_uom" sum="Effective Hours" optional="hide"/>
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress &gt;= 100" decoration-warning="progress &gt;= 80 and progress &lt; 100"/>
<field name="subtask_effective_hours" string="Sub-tasks Hours Spent" widget="timesheet_uom" sum="Sub-tasks Hours Spent" optional="hide"/>
<field name="total_hours_spent" string="Total Hours" widget="timesheet_uom" sum="Total Hours" optional="hide"/>
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress &gt;= 100" decoration-warning="progress &gt;= 80 and progress &lt; 100"/>
<field name="progress" widget="progressbar" optional="hide"/>
</xpath>
<xpath expr="//notebook/page[@name='description_page']" position="after">
@@ -245,9 +245,9 @@
<field name="allow_subtasks" invisible="1" />
<field name="planned_hours" widget="timesheet_uom_no_toggle" sum="Initially Planned Hours" optional="hide"/>
<field name="effective_hours" widget="timesheet_uom" sum="Effective Hours" optional="hide"/>
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress &gt;= 100" decoration-warning="progress &gt;= 80 and progress &lt; 100"/>
<field name="subtask_effective_hours" widget="timesheet_uom" attrs="{'invisible' : [('allow_subtasks', '=', False)]}" optional="hide"/>
<field name="total_hours_spent" widget="timesheet_uom" attrs="{'invisible' : [('allow_subtasks', '=', False)]}" optional="hide"/>
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress &gt;= 100" decoration-warning="progress &gt;= 80 and progress &lt; 100"/>
<field name="progress" widget="progressbar" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
</xpath>
</field>
@@ -351,6 +351,7 @@
</xpath>
<xpath expr="//filter[@name='late']" position='after'>
<filter string="Tasks in Overtime" name="overtime" domain="[('overtime', '&gt;', 0)]"/>
<filter string="Tasks Soon in Overtime" name="remaining_hours_percentage" domain="[('remaining_hours_percentage', '&lt;=', 0.2)]"/>
</xpath>
</field>
</record>
-1
View File
@@ -34,7 +34,6 @@
'views/project_project_stage_views.xml',
'wizard/project_share_wizard_views.xml',
'views/project_collaborator_views.xml',
'wizard/project_report_wizard_views.xml',
'views/project_views.xml',
'views/project_milestone_views.xml',
'views/res_partner_views.xml',
+25 -8
View File
@@ -308,16 +308,16 @@ class Project(models.Model):
"with Tasks (or optionally Issues if the Issue Tracker module is installed).")
alias_value = fields.Char(string='Alias email', compute='_compute_alias_value')
privacy_visibility = fields.Selection([
('followers', 'Invited employees'),
('employees', 'All employees'),
('portal', 'Invited portal users and all employees'),
('followers', 'Invited internal users'),
('employees', 'All internal users'),
('portal', 'Invited portal users and all internal users'),
],
string='Visibility', required=True,
default='portal',
help="Defines the visibility of the tasks of the project:\n"
"- Invited employees: employees may only see the followed project and tasks.\n"
"- All employees: employees may see all project and tasks.\n"
"- Invited portal users and all employees: employees may see everything."
"- Invited internal users: internal users may only see the followed project and tasks.\n"
"- All internal users: internal users may see all project and tasks.\n"
"- Invited portal users and all internal users: internal users may see everything."
" Invited portal users may see project and tasks followed by.\n"
" them or by someone of their company.")
doc_count = fields.Integer(compute='_compute_attached_docs_count', string="Number of documents attached")
@@ -531,6 +531,7 @@ class Project(models.Model):
project.message_subscribe(partner_ids=follower.partner_id.ids, subtype_ids=follower.subtype_ids.ids)
if 'tasks' not in default:
self.map_tasks(project.id)
return project
@api.model
@@ -857,7 +858,7 @@ class Project(models.Model):
'number': f'{round(100 * self.rating_avg_percentage, 2)} %',
'action_type': 'object',
'action': 'action_view_all_rating',
'show': self.rating_active and self.rating_count > 0,
'show': self.rating_active,
'sequence': 15,
})
if self.user_has_groups('project.group_project_user'):
@@ -1124,7 +1125,7 @@ class Task(models.Model):
working_days_close = fields.Float(compute='_compute_elapsed', string='Working Days to Close', store=True, group_operator="avg")
# customer portal: include comment and incoming emails in communication history
website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
is_private = fields.Boolean(compute='_compute_is_private')
is_private = fields.Boolean(compute='_compute_is_private', search='_search_is_private')
allow_milestones = fields.Boolean(related='project_id.allow_milestones')
milestone_id = fields.Many2one(
'project.milestone',
@@ -1153,6 +1154,7 @@ class Task(models.Model):
column2="task_id", string="Block", copy=False,
domain="[('allow_task_dependencies', '=', True), ('id', '!=', id)]")
dependent_tasks_count = fields.Integer(string="Dependent Tasks", compute='_compute_dependent_tasks_count')
is_blocked = fields.Boolean(compute='_compute_is_blocked', store=True, recursive=True)
# Project sharing fields
display_parent_task_button = fields.Boolean(compute='_compute_display_parent_task_button', compute_sudo=True)
@@ -1277,6 +1279,16 @@ class Task(models.Model):
for task in self:
task.is_private = not task.project_id and not task.parent_id
def _search_is_private(self, operator, value):
if not isinstance(value, bool):
raise ValueError(_('Value should be True or False (not %s)'), value)
if operator not in ['=', '!=']:
raise NotImplementedError(_('Operation should be = or != (not %s)'), value)
if (operator == '=' and value) or (operator == '!=' and not value):
return [('project_id', '=', False)]
else:
return [('project_id', '!=', False)]
@api.depends('parent_id.ancestor_id')
def _compute_ancestor_id(self):
for task in self:
@@ -1455,6 +1467,11 @@ class Task(models.Model):
for task in tasks_with_dependency:
task.dependent_tasks_count = dependent_tasks_count_dict.get(task.id, 0)
@api.depends('depend_on_ids.is_closed', 'depend_on_ids.is_blocked')
def _compute_is_blocked(self):
for task in self:
task.is_blocked = any(not blocking_task.is_closed or blocking_task.is_blocked for blocking_task in task.depend_on_ids)
@api.depends('partner_id.email')
def _compute_partner_email(self):
for task in self:
@@ -43,6 +43,5 @@ access_project_collaborator_user,project.collaborator.user,model_project_collabo
access_project_collaborator_portal,project.collaborator.portal,model_project_collaborator,base.group_portal,1,0,0,0
access_project_share_manager,project.share.wizard.manager,model_project_share_wizard,project.group_project_manager,1,1,1,0
access_project_personal_stage,project.personal.stage.user,model_project_task_stage_personal,base.group_user,1,1,1,1
access_project_report_wizard_manager,project.report.wizard.project.manager,model_project_report_wizard,project.group_project_manager,1,1,1,0
access_project_milestone_reach_wizard_project_user,project.milestone.reach.wizard.project.user,model_project_milestone_reach_wizard,project.group_project_user,1,1,1,0
access_project_milestone_reach_line_wizard_project_user,project.milestone.reach.line.wizard.project.user,model_project_milestone_reach_line_wizard,project.group_project_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
43 access_project_collaborator_portal project.collaborator.portal model_project_collaborator base.group_portal 1 0 0 0
44 access_project_share_manager project.share.wizard.manager model_project_share_wizard project.group_project_manager 1 1 1 0
45 access_project_personal_stage project.personal.stage.user model_project_task_stage_personal base.group_user 1 1 1 1
access_project_report_wizard_manager project.report.wizard.project.manager model_project_report_wizard project.group_project_manager 1 1 1 0
46 access_project_milestone_reach_wizard_project_user project.milestone.reach.wizard.project.user model_project_milestone_reach_wizard project.group_project_user 1 1 1 0
47 access_project_milestone_reach_line_wizard_project_user project.milestone.reach.line.wizard.project.user model_project_milestone_reach_line_wizard project.group_project_user 1 1 1 0
@@ -123,12 +123,12 @@
<field name="arch" type="xml">
<tree string="Tasks" sample="1" delete="0" import="0">
<field name="is_closed" invisible="1" />
<field name="allow_subtasks" invisible="1" />
<field name="sequence" invisible="1" readonly="1"/>
<field name="allow_milestones" invisible="1"/>
<field name="priority" widget="priority" optional="show" nolabel="1"/>
<field name="name" widget="name_with_subtask_count"/>
<field name="child_text" invisible="1"/>
<field name="allow_subtasks" invisible="1" />
<field name="name" widget="name_with_subtask_count"/>
<field name="company_id" invisible="1"/>
<field name="milestone_id" attrs="{'column_invisible': [('allow_milestones', '=', False)]}"/>
<field name="partner_id" optional="hide"/>
+45 -25
View File
@@ -16,7 +16,7 @@
<field name="model">project.task</field>
<field name="arch" type="xml">
<search string="Tasks">
<field name="name" string="Task"/>
<field name="name" string="Task" filter_domain="['|', ('name', 'ilike', self), ('id', 'ilike', self)]"/>
<field name="tag_ids"/>
<field name="user_ids" filter_domain="[('user_ids', 'ilike', self), ('user_ids.active', 'in', [True, False])]"/>
<field string="Project" name="display_project_id"/>
@@ -32,13 +32,24 @@
<filter string="High Priority" name="high_priority" domain="[('priority', '=', 1)]"/>
<filter string="Low Priority" name="low_priority" domain="[('priority', '=', 0)]"/>
<separator/>
<filter string="Open" name="open_tasks" domain="[('is_closed', '=', False)]"/>
<filter string="Closed" name="closed_tasks" domain="[('is_closed', '!=', False)]"/>
<filter string="Starred" name="starred" domain="[('priority', '=', 1)]"/>
<filter string="Not Starred" name="not_starred" domain="[('priority', '=', 0)]"/>
<separator/>
<filter string="Tasks Due Today" name="tasks_due_today" domain="[('date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Late Milestones" name="late_milestone" domain="[('is_closed', '=', False), ('has_late_and_unreached_milestone', '=', True)]" groups="project.group_project_milestone"/>
<filter string="Blocked" name="blocked" domain="[('is_blocked', '=', True)]" groups="project.group_project_task_dependencies"/>
<filter string="Not Blocked" name="not_blocked" domain="[('is_blocked', '=', False), ('is_private', '=', False)]" groups="project.group_project_task_dependencies"/>
<separator groups="project.group_project_task_dependencies"/>
<filter string="Blocking" name="blocking" domain="[('is_closed', '=', False), ('dependent_ids', '!=', False)]" groups="project.group_project_task_dependencies"/>
<filter string="Not Blocking" name="not_blocking" domain="['|', ('is_closed', '=', True), ('dependent_ids', '=', False), ('is_private', '=', False)]" groups="project.group_project_task_dependencies"/>
<separator groups="project.group_project_task_dependencies"/>
<filter string="Late Tasks" name="late" domain="[('date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"/>
<separator/>
<filter string="Open Tasks" name="open_tasks" domain="[('is_closed', '=', False)]"/>
<filter string="Closed Tasks" name="closed_tasks" domain="[('is_closed', '=', True)]"/>
<filter string="Closed Last 7 Days" name="closed_last_7_days" domain="[('is_closed', '=', True), ('date_last_stage_update', '&gt;', datetime.datetime.now() - relativedelta(days=7))]"/>
<filter string="Closed Last 30 Days" name="closed_last_30_days" domain="[('is_closed', '=', True), ('date_last_stage_update', '&gt;', datetime.datetime.now() - relativedelta(days=30))]"/>
<separator/>
<filter name="rating_satisfied" string="Satisfied" domain="[('rating_avg', '&gt;=', 3.66)]" groups="project.group_project_rating"/>
<filter name="rating_okay" string="Okay" domain="[('rating_avg', '&lt;', 3.66), ('rating_avg', '&gt;=', 2.33)]" groups="project.group_project_rating"/>
<filter name="dissatisfied" string="Dissatisfied" domain="[('rating_avg', '&lt;', 2.33), ('rating_last_value', '!=', 0)]" groups="project.group_project_rating"/>
@@ -1139,7 +1150,11 @@
<field name="is_closed" invisible="1"/>
<field name="allow_milestones" invisible="1"/>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="priority" widget="priority" optional="show" nolabel="1"/>
<field name="id" optional="hide"/>
<field name="child_text" invisible="1"/>
<field name="allow_subtasks" invisible="1"/>
<field name="name" widget="name_with_subtask_count"/>
<field name="display_project_id" string="Project" optional="hide"/>
<field name="milestone_id"
optional="hide"
@@ -1149,10 +1164,14 @@
<field name="partner_id" optional="hide"/>
<field name="user_ids" widget="many2many_avatar_user" optional="show" domain="[('share', '=', False), ('active', '=', True)]"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
<field name="activity_ids" widget="list_activity" optional="hide"/>
<field name="activity_ids" string="Next Activity" widget="list_activity" optional="hide"/>
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="show"/>
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="hide"/>
<field name="kanban_state" required="0" widget="state_selection" optional="show" options="{'hide_label': True}" nolabel="1" class="d-flex justify-content-center"/>
<field name="rating_last_text" string="Rating" decoration-danger="rating_last_text == 'ko'"
decoration-warning="rating_last_text == 'ok'" decoration-success="rating_last_text == 'top'"
attrs="{'invisible': [('rating_last_text', '=', 'none')]}"
class="font-weight-bold" widget="badge" optional="hide"/>
<field name="kanban_state" widget="state_selection" optional="show" options="{'hide_label': True}" nolabel="1"/>
<field name="stage_id" optional="show"/>
<button name="action_open_task" type="object" title="View Task" string="View Task" class="btn btn-link float-end"/>
</tree>
@@ -1165,18 +1184,31 @@
<field name="parent_id" invisible="1" />
<field name="display_project_id" invisible="1" />
<field name="is_closed" invisible="1" />
<field name="name" />
<field name="priority" widget="priority" optional="show" nolabel="1"/>
<field name="child_text" invisible="1"/>
<field name="allow_subtasks" invisible="1"/>
<field name="name" widget="name_with_subtask_count"/>
<field name="id" optional="hide"/>
<field name="project_id" optional="hide" />
<field name="milestone_id"
optional="hide"
context="{'default_project_id': project_id if not parent_id or not display_project_id else display_project_id}"
attrs="{'invisible': [('allow_milestones', '=', False)], 'column_invisible': [('parent.allow_milestones', '=', False)]}"
/>
<field name="partner_id" optional="hide"/>
<field name="parent_id" optional="hide" attrs="{'invisible': [('allow_subtasks', '=', False)]}" groups="base.group_no_one"/>
<field name="user_ids" widget="many2many_avatar_user" optional="show" domain="[('share', '=', False), ('active', '=', True)]"/>
<field name="company_id" optional="hide" groups="base.group_multi_company" />
<field name="activity_ids" widget="list_activity" optional="hide"/>
<field name="activity_ids" string="Next Activity" widget="list_activity" optional="hide"/>
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="show" />
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="hide"/>
<field name="rating_last_text" string="Rating" decoration-danger="rating_last_text == 'ko'"
decoration-warning="rating_last_text == 'ok'" decoration-success="rating_last_text == 'top'"
attrs="{'invisible': [('rating_last_text', '=', 'none')]}"
class="font-weight-bold" widget="badge" optional="hide"/>
<field name="legend_normal" invisible="1"/>
<field name="legend_done" invisible="1"/>
<field name="legend_blocked" invisible="1"/>
<field name="kanban_state" widget="state_selection" optional="show" options="{'hide_label': True}" nolabel="1"/>
<field name="stage_id" optional="show" />
<button class="oe_link float-right" string="View Task" name="action_open_task" type="object"/>
@@ -1409,18 +1441,15 @@
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<tree string="Tasks" multi_edit="1" sample="1" js_class="project_list">
<field name="legend_normal" invisible="1"/>
<field name="legend_done" invisible="1"/>
<field name="legend_blocked" invisible="1"/>
<field name="message_needaction" invisible="1" readonly="1"/>
<field name="is_closed" invisible="1" />
<field name="allow_subtasks" invisible="1" />
<field name="sequence" invisible="1" readonly="1"/>
<field name="allow_milestones" invisible="1"/>
<field name="priority" widget="priority" optional="show" nolabel="1"/>
<field name="id" optional="hide"/>
<field name="name" widget="name_with_subtask_count"/>
<field name="child_text" invisible="1"/>
<field name="allow_subtasks" invisible="1"/>
<field name="name" widget="name_with_subtask_count"/>
<field name="project_id" widget="project_private_task" optional="show" readonly="1"/>
<field name="milestone_id" attrs="{'invisible': [('allow_milestones', '=', False)]}" context="{'default_project_id': project_id}" groups="project.group_project_milestone"/>
<field name="partner_id" optional="hide"/>
@@ -1435,6 +1464,9 @@
decoration-warning="rating_last_text == 'ok'" decoration-success="rating_last_text == 'top'"
attrs="{'invisible': ['|', ('rating_active', '=', False), ('rating_last_text', '=', 'none')]}"
class="font-weight-bold" widget="badge" optional="hide" groups="project.group_project_rating"/>
<field name="legend_normal" invisible="1"/>
<field name="legend_done" invisible="1"/>
<field name="legend_blocked" invisible="1"/>
<field name="kanban_state" widget="state_selection" optional="show" options="{'hide_label': True}" nolabel="1"/>
<field name="stage_id" invisible="context.get('set_visible',False)" optional="show" readonly="not context.get('default_project_id')"/>
<field name="recurrence_id" invisible="1" />
@@ -1730,24 +1762,12 @@
parent="menu_project_report"
sequence="10"/>
<menuitem id="project_burndown_chart_report_menu_action"
name="Burndown Chart"
action="project.project_burndown_chart_report_action"
parent="menu_project_report"
sequence="11"/>
<menuitem id="rating_rating_menu_project"
action="rating_rating_action_project_report"
parent="menu_project_report"
groups="project.group_project_rating"
sequence="51"/>
<menuitem id="project_update_menu_action"
name="Project Updates"
action="project.project_update_all_report_action"
parent="menu_project_report"
sequence="60"/>
<record id="project_view_kanban_inherit_project" model="ir.ui.view">
<field name="name">project.kanban.inherit.project</field>
<field name="model">project.project</field>
-1
View File
@@ -5,4 +5,3 @@ from . import project_delete_wizard
from . import project_milestone_reach_wizard
from . import project_task_type_delete
from . import project_share_wizard
from . import project_report_wizard
@@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class ProjectReportWizard(models.TransientModel):
_name = 'project.report.wizard'
_description = 'Project Report Wizard'
project_id = fields.Many2one('project.project', required=True)
report_type = fields.Selection([
('burndown', 'Burndown Chart'), ('project_update', 'Project Update')
], string='Report Type', required=True, default='burndown')
def action_burndown_report_view(self):
action = self.env['ir.actions.act_window']._for_xml_id('project.action_project_task_burndown_chart_report')
action['context'] = {'search_default_project_id': self.project_id.id}
return action
def action_project_update_report_view(self):
action = self.env['ir.actions.act_window']._for_xml_id('project.project_update_all_action')
action['context'] = {'active_id': self.project_id.id, 'search_default_project_id': self.project_id.id}
return action
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="project_report_wizard_form" model="ir.ui.view">
<field name="name">project.report.wizard.form</field>
<field name="model">project.report.wizard</field>
<field name="arch" type="xml">
<form string="Project">
<group>
<group>
<field name="report_type" invisible="1"/>
<field name="project_id"/>
</group>
</group>
<footer>
<button string="Go to Project Updates" name="action_project_update_report_view" type="object" class="btn-primary" attrs="{'invisible': [('report_type', '!=', 'project_update')]}"/>
<button string="Go to Burndown Chart" name="action_burndown_report_view" type="object" class="btn-primary" attrs="{'invisible': [('report_type', '!=', 'burndown')]}"/>
<button string="Discard" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="project_update_all_report_action" model="ir.actions.act_window">
<field name="name">Select a Project</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">project.report.wizard</field>
<field name="view_mode">form</field>
<field name="context">{'default_report_type': 'project_update'}</field>
<field name="view_id" ref="project_report_wizard_form"/>
<field name="target">new</field>
</record>
<record id="project_burndown_chart_report_action" model="ir.actions.act_window">
<field name="name">Select a Project</field>
<field name="res_model">project.report.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="project_report_wizard_form"/>
<field name="context">{'default_report_type': 'burndown'}</field>
<field name="target">new</field>
</record>
</odoo>
@@ -8,7 +8,7 @@
<field name="priority">40</field>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_view_analytic_account_entries']" position="after">
<button name="action_open_project_expenses" type="object" class="oe_stat_button" icon="fa-money" attrs="{'invisible': [('expenses_count', '=', 0)]}" groups="hr_expense.group_hr_expense_team_approver">
<button name="action_open_project_expenses" type="object" class="oe_stat_button" icon="fa-money" groups="hr_expense.group_hr_expense_team_approver">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="expenses_count" nolabel="1"/>
+2 -2
View File
@@ -8,7 +8,7 @@
<field name="arch" type="xml">
<xpath expr="//button[@name='action_view_analytic_account_entries']" position="after">
<button class="oe_stat_button" type="object" name="action_view_mrp_production"
icon="fa-wrench" attrs="{'invisible': [('production_count', '=', 0)]}"
icon="fa-wrench"
groups="mrp.group_mrp_user">
<field string="Manufacturing Orders" name="production_count" widget="statinfo"/>
</button>
@@ -18,7 +18,7 @@
<field string="Work Orders" name="workorder_count" widget="statinfo"/>
</button>
<button class="oe_stat_button" type="object" name="action_view_mrp_bom"
icon="fa-flask" attrs="{'invisible': [('bom_count', '=', 0)]}"
icon="fa-flask"
groups="mrp.group_mrp_user">
<field string="Bills of Materials" name="bom_count" widget="statinfo"/>
</button>
+1
View File
@@ -19,6 +19,7 @@ This module allows to generate a project/task from sales orders.
'views/sale_order_views.xml',
'views/sale_project_portal_templates.xml',
'views/project_sharing_views.xml',
'views/project_views.xml',
],
'assets': {
'web.assets_backend': [
+1 -1
View File
@@ -468,7 +468,7 @@ class ProjectTask(models.Model):
sale_order_id = fields.Many2one('sale.order', 'Sales Order', compute='_compute_sale_order_id', store=True, help="Sales order to which the task is linked.")
sale_line_id = fields.Many2one(
'sale.order.line', 'Sales Order Item',
copy=False, tracking=True, index='btree_not_null', recursive=True,
copy=True, tracking=True, index='btree_not_null', recursive=True,
compute='_compute_sale_line', store=True, readonly=False,
domain="[('company_id', '=', company_id), ('is_service', '=', True), ('order_partner_id', '=?', partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done'])]",
help="Sales Order Item to which the time spent on this task will be added, in order to be invoiced to your customer.")
@@ -13,8 +13,6 @@
<field name="invoice_policy" position="after">
<field name="service_policy" string="Invoicing Policy" attrs="{'invisible': [('type', '!=', 'service')], 'required': [('type', '=', 'service')]}"/>
<field name="service_tracking" required="1" attrs="{'invisible': [('type', '!=', 'service')]}"/>
</field>
<field name="product_tooltip" position="after">
<field name="project_id" context="{'default_allow_billable': True}" attrs="{'invisible':[('service_tracking','!=','task_global_project')], 'required':[('service_tracking','==','task_global_project')]}"/>
<field name="project_template_id" context="{'active_test': False, 'default_allow_billable': True}" attrs="{'invisible':[('service_tracking','not in',['task_in_project', 'project_only'])]}"/>
</field>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="project.open_view_project_all_config" model="ir.actions.act_window">
<field name="context">{'default_allow_billable': True}</field>
</record>
</odoo>
@@ -41,7 +41,7 @@
<field name="employee_id" options="{'no_create': True}"/>
<field name="sale_line_id" attrs="{'required': True}" options="{'no_create': True}"/>
<field name="price_unit" widget="monetary" force_save="1" options="{'currency_field': 'currency_id'}"/>
<field name="cost"/>
<field name="cost" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="is_cost_changed" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="cost_currency_id" invisible="1"/>
@@ -108,11 +108,18 @@
<field name="name">sale.service.form.view.inherit</field>
<field name="model">project.task</field>
<field name="groups_id" eval="[(4, ref('base.group_user'))]"/>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="inherit_id" ref="hr_timesheet.view_task_form2_inherited"/>
<field name="arch" type="xml">
<xpath expr="//header" position='inside'>
<field name="allow_billable" invisible="1"/>
</xpath>
<xpath expr="//field[@name='child_ids']/tree/field[@name='remaining_hours']" position="after">
<field name="remaining_hours_so" string="Remaining Hours on SO" widget="timesheet_uom" optional="hide"/>
</xpath>
<xpath expr="//field[@name='depend_on_ids']/tree/field[@name='remaining_hours']" position="after">
<field name="remaining_hours_so" string="Remaining Hours on SO" widget="timesheet_uom" optional="hide"/>
</xpath>
</field>
</record>
@@ -887,7 +887,7 @@ tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_p
content: _t('See Project'),
position: 'right',
},
tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_panel .breadcrumb:contains("the_flow.project")')),
tour.stepUtils.autoExpandMoreButtons('.o_control_panel .breadcrumb:contains("the_flow.project")'),
{
trigger: '.oe_button_box .oe_stat_button:has(span:contains("Recorded"))',
extra_trigger: '.o_form_readonly',