[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:
@@ -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:
|
||||
|
||||
@@ -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 >= 100" decoration-warning="progress >= 80 and progress < 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 >= 100" decoration-warning="progress >= 80 and progress < 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 >= 100" decoration-warning="progress >= 80 and progress < 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 >= 100" decoration-warning="progress >= 80 and progress < 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', '>', 0)]"/>
|
||||
<filter string="Tasks Soon in Overtime" name="remaining_hours_percentage" domain="[('remaining_hours_percentage', '<=', 0.2)]"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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"/>
|
||||
|
||||
@@ -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', '<', 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', '>', 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', '>', datetime.datetime.now() - relativedelta(days=30))]"/>
|
||||
<separator/>
|
||||
<filter name="rating_satisfied" string="Satisfied" domain="[('rating_avg', '>=', 3.66)]" groups="project.group_project_rating"/>
|
||||
<filter name="rating_okay" string="Okay" domain="[('rating_avg', '<', 3.66), ('rating_avg', '>=', 2.33)]" groups="project.group_project_rating"/>
|
||||
<filter name="dissatisfied" string="Dissatisfied" domain="[('rating_avg', '<', 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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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': [
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user