[IMP] (sale_,)project,hr_timesheet: improve generic ux of project

Purpose of this commit to improve generic usage of project
app.

So, in this commit done following changes:

- add 'my department' filter in project search view.
- add 'my department's tasks' and 'my department's projects filter
  in task search view.
- add quick search, filter and groupby in task analysis report search view
  to make task analysis report search view same as task's search view.
- improve portal template of my/tasks page.
- project.task form view > timesheets: display the employee avatar
  on mobile.
- make 'Task: Rating Request' template auto_delete false.
- settings: make following features disabled by default: sub-tasks,
  task dependencies, recurring tasks.
- project.update kanban list: align the stat buttons with the kanban
  list cards.
- add sample data to the project stages > list view.
- project.task quickcreate: enable the creation of new users on the
  fly.
- move tags quick search below project.
- add title to show  project full name on hover of project name in kanban
  view of project.
- copy user_ids for task in recurrence.
- improve UI visibility of planned_hours and subtask_planned_hours field in
  timesheet page of task form view.
- improve project sharing wizard and project.collaborator tree
  view.
- add grid,kanban,pivot and graph views to 'hours spent on
  sub-tasks' button's action.

closes: #83482
task-2742725

Related: odoo/enterprise#23788
Related: odoo/upgrade#3480
Signed-off-by: Laurent Stukkens (ltu) <ltu@odoo.com>
This commit is contained in:
Kartik Chavda
2022-05-03 17:53:45 +02:00
parent a05874f808
commit c0da9d934c
23 changed files with 199 additions and 82 deletions
+1
View File
@@ -37,6 +37,7 @@ up a management by affair.
'views/hr_views.xml',
'data/hr_timesheet_data.xml',
'views/project_sharing_views.xml',
'views/rating_views.xml',
],
'demo': [
'data/hr_timesheet_demo.xml',
+13 -9
View File
@@ -319,16 +319,20 @@ class Task(models.Model):
def action_view_subtask_timesheet(self):
self.ensure_one()
tasks = self.with_context(active_test=False)._get_all_subtasks()
return {
'type': 'ir.actions.act_window',
'name': _('Timesheets'),
'res_model': 'account.analytic.line',
'view_mode': 'list,form',
'context': {
'default_project_id': self.project_id.id
},
action = self.env["ir.actions.actions"]._for_xml_id("hr_timesheet.timesheet_action_all")
graph_view_id = self.env.ref("hr_timesheet.view_hr_timesheet_line_graph_by_employee").id
new_views = []
for view in action['views']:
if view[1] == 'graph':
view = (graph_view_id, 'graph')
new_views.insert(0, view) if view[1] == 'tree' else new_views.append(view)
action.update({
'display_name': _('Timesheets'),
'context': {'default_project_id': self.project_id.id, 'grid_range': 'week'},
'domain': [('project_id', '!=', False), ('task_id', 'in', tasks.ids)],
}
'views': new_views,
})
return action
def _get_timesheet(self):
# Is override in sale_timesheet
@@ -38,11 +38,13 @@
<xpath expr="//filter[@name='late']" position="after">
<filter string="Tasks in Overtime" name="overtime" domain="[('overtime', '&gt;', 0)]"/>
</xpath>
<xpath expr="//filter[@name='my_tasks']" position="before">
<xpath expr="//filter[@name='my_favorite_projects']" position="after">
<filter string="My Team's Projects" name="my_team_projects" domain="[('project_id.user_id.employee_id.parent_id.user_id', '=', uid), ('project_id.user_id', '!=', uid), ('user_ids', '!=', uid), ('user_ids', '!=', False)]"/>
<filter string="My Department's Projects" name="my_department" domain="[('project_id.user_id.employee_id.member_of_department', '=', True)]"/>
</xpath>
<xpath expr="//filter[@name='my_tasks']" position="after">
<xpath expr="//filter[@name='followed_by_me']" position="after">
<filter string="My Team's Tasks" name="my_team_tasks" domain="[('user_ids.employee_id.parent_id.user_id', '=', uid)]" />
<filter string="My Department's Tasks" name="my_department" domain="[('user_ids.employee_id.member_of_department', '=', True)]"/>
</xpath>
</field>
</record>
@@ -117,6 +117,16 @@
</field>
</record>
<record id="view_hr_timesheet_line_graph_by_employee" model="ir.ui.view">
<field name="name">account.analytic.line.graph.by.employee</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.view_hr_timesheet_line_graph_all"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<field name="project_id" position="replace"/>
</field>
</record>
<record id="hr_timesheet_line_form" model="ir.ui.view">
<field name="name">account.analytic.line.form</field>
<field name="model">account.analytic.line</field>
@@ -52,15 +52,14 @@
<attribute name="t-value">8</attribute>
</xpath>
<xpath expr="//thead/tr/th[@name='project_portal_assignees']" position="after">
<th t-if="is_uom_day">Days Spent</th>
<th t-else="">Hours Spent</th>
<th t-if="is_uom_day" class="text-right">Days Spent</th>
<th t-else="" class="text-right">Hours Spent</th>
</xpath>
<xpath expr="//tbody/t/tr/td[@name='project_portal_assignees']" position="after">
<td>
<td class="text-right">
<t t-if="is_uom_day">
<t t-out="timesheet_ids._convert_hours_to_days(task.effective_hours)"/>
<span t-if="task.planned_hours > 0"> / <t t-out="timesheet_ids._convert_hours_to_days(task.planned_hours)"/></span>
<span> day(s)</span>
</t>
<t t-else="">
<span t-field="task.effective_hours" t-options='{"widget": "float_time"}'/>
@@ -68,7 +67,6 @@
/
<span t-field="task.planned_hours" t-options='{"widget": "float_time"}'/>
</t>
<span> hour(s)</span>
</t>
</td>
</xpath>
+9 -7
View File
@@ -131,13 +131,12 @@
<div class="o_td_label">
<label for="planned_hours" string="Allocated Hours" attrs="{'invisible': [('encode_uom_in_days', '=', True)]}"/>
<label for="planned_hours" string="Allocated Days" attrs="{'invisible': [('encode_uom_in_days', '=', False)]}"/>
<field name="planned_hours" class="oe_inline ml-2" widget="timesheet_uom_no_toggle"/>
<span attrs="{'invisible': ['|', ('allow_subtasks', '=', False), ('subtask_count', '=', 0)]}">
(incl. <field name="subtask_planned_hours" nolabel="1" groups="project.group_subtask_project" widget="timesheet_uom_no_toggle"/> on
<span class="font-weight-bold text-dark"> Sub-tasks</span>)
</span>
</div>
<field name="planned_hours" widget="timesheet_uom_no_toggle" nolabel="1"/>
<div class="o_td_label" groups="project.group_subtask_project" attrs="{'invisible': ['|', ('allow_subtasks', '=', False), ('subtask_count', '=', 0)]}">
<label for="subtask_planned_hours" string="Sub-tasks Planned Hours" attrs="{'invisible': [('encode_uom_in_days', '=', True)]}"/>
<label for="subtask_planned_hours" string="Sub-tasks Planned Days" attrs="{'invisible': [('encode_uom_in_days', '=', False)]}"/>
</div>
<field name="subtask_planned_hours" widget="timesheet_uom_no_toggle" nolabel="1" groups="project.group_subtask_project" attrs="{'invisible': ['|', ('allow_subtasks', '=', False), ('subtask_count', '=', 0)]}"/>
</group>
<group>
<field name="progress" widget="progressbar"/>
@@ -163,7 +162,6 @@
<kanban class="o_kanban_mobile">
<field name="date"/>
<field name="user_id"/>
<field name="employee_id" widget="many2one_avatar_employee"/>
<field name="name"/>
<field name="unit_amount" decoration-danger="unit_amount &gt; 24"/>
<field name="project_id"/>
@@ -173,6 +171,7 @@
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
<div class="row">
<div class="col-6">
<field name="employee_id" widget="many2one_avatar_employee"/>
<strong><span><t t-esc="record.employee_id.value"/></span></strong>
</div>
<div class="col-6 float-end text-right">
@@ -348,6 +347,7 @@
<field name="arch" type="xml">
<xpath expr="//filter[@name='followed_by_me']" position='after'>
<filter string="My Team's Tasks" name="my_team_tasks" domain="[('user_ids.employee_parent_id.user_id', '=', uid)]"/>
<filter string="My Department's Tasks" name="my_department" domain="[('user_ids.employee_id.member_of_department', '=', True)]"/>
</xpath>
<xpath expr="//filter[@name='late']" position='after'>
<filter string="Tasks in Overtime" name="overtime" domain="[('overtime', '&gt;', 0)]"/>
@@ -362,6 +362,7 @@
<field name="arch" type="xml">
<xpath expr="//filter[@name='my_favorite_projects']" position='after'>
<filter string="My Team's Projects" name="my_teams_projects" domain="[('project_id.user_id.employee_parent_id.user_id', '=', uid)]"/>
<filter string="My Department's Projects" name="my_department" domain="[('manager_id.employee_id.member_of_department', '=', True)]"/>
</xpath>
</field>
</record>
@@ -373,6 +374,7 @@
<field name="arch" type="xml">
<filter name="followed_by_me" position='before'>
<filter string="My Team's Projects" name="my_team_projects" domain="[('user_id.employee_parent_id.user_id', '=', uid)]"/>
<filter string="My Department" name="my_department" domain="[('user_id.employee_id.member_of_department', '=', True)]"/>
</filter>
<filter name="late_milestones" position="before">
<filter string="Projects in Overtime" name="projects_in_overtime" domain="[('is_project_overtime', '=', True)]"/>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="rating_rating_view_search_project_inherited" model="ir.ui.view">
<field name="name">rating.rating.search.project.inherited</field>
<field name="model">rating.rating</field>
<field name="inherit_id" ref="project.rating_rating_view_search_project"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='my_ratings']" position="after">
<filter string="My Team's Ratings" name="my_team_rating" domain="[('rated_partner_id.user_ids.employee_parent_id.user_id', '=', uid)]"/>
<filter string="My Department's Ratings" name="my_department_rating" domain="[('rated_partner_id.user_ids.employee_id.member_of_department', '=', True)]"/>
</xpath>
</field>
</record>
</odoo>
-6
View File
@@ -7,12 +7,6 @@
<field name="groups_id" eval="[Command.link(ref('group_project_user'))]"/>
</record>
<!-- Groups : Add subtasks, recurring tasks and task dependencies by default -->
<record id="base.group_user" model="res.groups">
<field name="implied_ids"
eval="[Command.link(ref('group_subtask_project')), Command.link(ref('group_project_recurring_tasks')), Command.link(ref('group_project_task_dependencies'))]"/>
</record>
<!-- Categories -->
<record id="project_tags_00" model="project.tags">
<field name="name">Bug</field>
+1 -1
View File
@@ -1341,7 +1341,7 @@ class Task(models.Model):
if recurrence_left == 0:
recurrence_title = _('There are no more occurrences.')
else:
recurrence_title = _('Next Occurrences:')
recurrence_title = _('A new task will be created on the following dates:')
task.recurrence_message = '<p><span class="fa fa-check-circle"></span> %s</p><ul>' % recurrence_title
task.recurrence_message += ''.join(['<li>%s</li>' % date.strftime(date_format) for date in recurring_dates[:5]])
if task.repeat_type == 'after' and recurrence_left > 5 or task.repeat_type == 'forever' or len(recurring_dates) > 5:
@@ -10,6 +10,7 @@ class ProjectCollaborator(models.Model):
project_id = fields.Many2one('project.project', 'Project Shared', domain=[('privacy_visibility', '=', 'portal')], required=True, readonly=True)
partner_id = fields.Many2one('res.partner', 'Collaborator', required=True, readonly=True)
partner_email = fields.Char(related='partner_id.email')
_sql_constraints = [
('unique_collaborator', 'UNIQUE(project_id, partner_id)', 'A collaborator cannot be selected more than once in the project sharing access. Please remove duplicate(s) and try again.'),
@@ -146,7 +146,7 @@ class ProjectTaskRecurrence(models.Model):
return ['message_partner_ids', 'company_id', 'description', 'displayed_image_id', 'email_cc',
'parent_id', 'partner_email', 'partner_id', 'partner_phone', 'planned_hours',
'project_id', 'display_project_id', 'project_privacy_visibility', 'sequence', 'tag_ids', 'recurrence_id',
'name', 'recurring_task', 'analytic_account_id']
'name', 'recurring_task', 'analytic_account_id', 'user_ids']
def _get_weekdays(self, n=1):
self.ensure_one()
@@ -209,7 +209,6 @@ class ProjectTaskRecurrence(models.Model):
field: value[0] if isinstance(value, tuple) else value for field, value in task_values.items()
}
create_values['stage_id'] = task.project_id.type_ids[0].id if task.project_id.type_ids else task.stage_id.id
create_values['user_ids'] = False
return create_values
def _create_subtasks(self, task, new_task, depth=3):
+13 -2
View File
@@ -3,7 +3,7 @@
from odoo import fields, models, tools
from odoo.addons.rating.models.rating_data import RATING_LIMIT_MIN
from odoo.addons.rating.models.rating_data import RATING_LIMIT_MIN, RATING_TEXT
class ReportProjectTaskUser(models.Model):
_name = "report.project.task.user"
@@ -51,6 +51,18 @@ class ReportProjectTaskUser(models.Model):
column1='project_task_id', column2='project_tags_id',
string='Tags', readonly=True)
parent_id = fields.Many2one('project.task', string='Parent Task', readonly=True)
# We are explicitly not using a related field in order to prevent the recomputing caused by the depends as the model is a report.
rating_last_text = fields.Selection(RATING_TEXT, string="Rating Last Text", compute="_compute_rating_last_text", search="_search_rating_last_text")
personal_stage_type_ids = fields.Many2many('project.task.type', relation='project_task_user_rel',
column1='task_id', column2='stage_id',
string="Personal Stage", readonly=True)
def _compute_rating_last_text(self):
for task_analysis in self:
task_analysis.rating_last_text = task_analysis.task_id.rating_last_text
def _search_rating_last_text(self, operator, value):
return [('task_id.rating_last_text', operator, value)]
def _select(self):
return """
@@ -109,7 +121,6 @@ class ReportProjectTaskUser(models.Model):
def _from(self):
return f"""
project_task t
LEFT JOIN project_task_user_rel tu on t.id=tu.task_id
LEFT JOIN rating_rating rt ON rt.res_id = t.id
AND rt.res_model = 'project.task'
AND rt.consumed = True
+23 -10
View File
@@ -43,43 +43,56 @@
<field name="model">report.project.task.user</field>
<field name="arch" type="xml">
<search string="Tasks Analysis">
<field name="name" string="Task"/>
<field name="tag_ids"/>
<field name="user_ids" context="{'active_test': False}"/>
<field name="project_id"/>
<field name="name"/>
<field name="stage_id"/>
<field name="user_ids"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="partner_id" operator="child_of"/>
<field name="active"/>
<field name="parent_id"/>
<field name="name" string="Tags" filter_domain="[('tag_ids', 'ilike', self)]"/>
<field name="rating_last_text"/>
<field name="date_assign"/>
<field name="date_end"/>
<field name="date_deadline"/>
<field name="date_last_stage_update"/>
<filter string="My Projects" name="own_projects" domain="[('project_id.user_id', '=', uid)]"/>
<filter string="My Tasks" name="my_tasks" domain="[('user_ids', 'in', uid)]"/>
<filter string="Followed Tasks" name="followed_by_me" domain="[('task_id.message_is_follower', '=', True)]"/>
<filter string="Unassigned" name="unassigned" domain="[('user_ids', '=', False)]"/>
<separator/>
<filter string="My Projects" name="own_projects" domain="[('project_id.user_id', '=', uid)]"/>
<filter string="My Favorite Projects" name="my_favorite_projects" domain="[('project_id.favorite_user_ids', 'in', [uid])]"/>
<separator/>
<filter string="High Priority" name="high_priority" domain="[('priority', '=', 1)]"/>
<filter string="Low Priority" name="low_priority" domain="[('priority', '=', 0)]"/>
<separator/>
<filter string="Tasks Late" name="late" domain="[('date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Open" name="open_tasks" domain="[('is_closed', '=', False)]"/>
<filter string="Closed" name="closed_tasks" domain="[('is_closed', '=', True)]"/>
<separator/>
<filter string="Unassigned Tasks" name="unassigned" domain="[('user_ids', '=', False)]"/>
<separator/>
<filter string="Open tasks" name="open_tasks" domain="[('is_closed', '=', False)]"/>
<filter string="Late Tasks" name="late" domain="[('date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"/>
<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"/>
<filter name="no_rating" string="No Rating" domain="[('rating_last_value', '=', 0)]" groups="project.group_project_rating"/>
<separator/>
<filter name="filter_date_deadline" date="date_deadline"/>
<filter name="filter_date_assign" date="date_assign"/>
<filter name="filter_date_last_stage_update" date="date_last_stage_update"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group expand="0" string="Extended Filters">
<field name="priority"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group expand="1" string="Group By">
<filter string="Project" name="project" context="{'group_by': 'project_id'}"/>
<filter string="Stage" name="Stage" context="{'group_by': 'stage_id'}"/>
<filter string="Personal Stage" name="personal_stage" context="{'group_by': 'personal_stage_type_ids'}"/>
<filter string="Assignees" name="User" context="{'group_by': 'user_ids'}"/>
<filter string="Project" name="project" context="{'group_by': 'project_id'}"/>
<filter string="Customer" name="Customer" context="{'group_by': 'partner_id'}"/>
<filter string="Kanban State" name="kanban_state" context="{'group_by': 'state'}"/>
<filter string="Deadline" name="deadline" context="{'group_by': 'date_deadline'}"/>
<filter string="Creation Date" name="group_create_date" context="{'group_by': 'create_date'}"/>
</group>
</search>
</field>
@@ -184,7 +184,7 @@ html .o_web_client > .o_action_manager > .o_action > .o_content > .o_controller_
border-width: 1px;
margin-top: -1px;
margin-left: -1px;
height: 56px;
height: 57.5px;
&:nth-child(3n){
border-right-width: 0px;
@@ -194,6 +194,11 @@ class TestProjectSubtasks(TestProjectCommon):
self.assertEqual(self.task_1.child_ids.stage_id.name, "New", "The stage of the child task should be the default one of the display project id, once set.")
def test_copy_project_with_subtasks(self):
# Enable the company setting
self.env['res.config.settings'].create({
'group_subtask_project': True
}).execute()
self.env['project.task'].with_context({'mail_create_nolog': True}).create({
'name': 'Parent Task',
'project_id': self.project_goats.id,
@@ -183,6 +183,8 @@ class TestTaskDependencies(TestProjectCommon):
"Copy should not alter the relation if the other task is in a different project")
def test_duplicate_project_with_subtask_dependencies(self):
self.project_goats.allow_task_dependencies = True
self.project_goats.allow_subtasks = True
parent_task = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
'name': 'Parent Task',
'project_id': self.project_goats.id,
@@ -21,8 +21,8 @@
<field name="model">project.collaborator</field>
<field name="arch" type="xml">
<tree string="Project Collaborators" create="0">
<field name="project_id" options="{'no_create': True}"/>
<field name="partner_id" options="{'no_create': True}"/>
<field name="partner_email"/>
</tree>
</field>
</record>
@@ -33,7 +33,7 @@
</template>
<template id="portal_my_tasks_priority_widget_template" name="Priority Widget Template">
<span t-attf-class="o_priority_star fa fa-star#{'' if task.priority == '1' else '-o'}"/>
<span t-attf-class="o_priority_star fa fa-star#{'' if task.priority == '1' else '-o'}" t-attf-title="Priority: {{'Important' if task.priority == '1' else 'Normal'}}"/>
</template>
<template id="portal_my_tasks_state_widget_template" name="Status Widget Template">
@@ -114,13 +114,12 @@
<t t-set="number_of_header" t-value="7"></t>
<!-- Computes the right colspan once and use it everywhere -->
<t t-set="grouped_tasks_colspan" t-value="number_of_header - 1 if groupby in group_by_in_header_list else number_of_header"></t>
<th class="text-left">Ref</th>
<th t-if="groupby != 'priority'">Priority</th>
<th t-attf-colspan="{{2 if groupby != 'priority' else 1}}"/>
<th>Name</th>
<th name="project_portal_assignees">Assignees</th>
<th t-if="groupby != 'status'">Status</th>
<th t-if="groupby != 'status'"/>
<th t-if="groupby != 'project'">Project</th>
<th t-if="groupby != 'stage'">Stage</th>
<th t-if="groupby != 'stage'" class="text-right">Stage</th>
</tr>
</thead>
<t t-foreach="grouped_tasks" t-as="tasks">
@@ -144,7 +143,7 @@
<td class="text-left">
#<span t-esc="task.id"/>
</td>
<td t-if="groupby != 'priority'">
<td t-if="groupby != 'priority'" class="text-right">
<t t-call="project.portal_my_tasks_priority_widget_template"/>
</td>
<td>
@@ -152,9 +151,12 @@
</td>
<td name="project_portal_assignees">
<t t-set="assignees" t-value="task.sudo().user_ids"/>
<span t-if="assignees" t-out="'%s%s' % (assignees[:1].name, ' + %s others' % len(assignees[1:]) if len(assignees.user_ids) > 1 else '')" t-att-title="'\n'.join(assignees[1:].mapped('name'))"/>
<div t-if="assignees" class="row flex-nowrap pl-3">
<img class="rounded-circle o_portal_contact_img mr-2" t-attf-src="#{image_data_uri(assignees[:1].avatar_1024)}" alt="User" style="width: 20px; height: 20px;"/>
<span t-out="'%s%s' % (assignees[:1].name, ' + %s others' % len(assignees[1:]) if len(assignees.user_ids) > 1 else '')" t-att-title="'\n'.join(assignees[1:].mapped('name'))"/>
</div>
</td>
<td t-if="groupby != 'status'">
<td t-if="groupby != 'status'" align="right">
<t t-call="project.portal_my_tasks_state_widget_template">
<t t-set="path" t-value="'tasks'"/>
</t>
@@ -162,8 +164,8 @@
<td t-if="groupby != 'project'">
<span class="badge badge-pill badge-info mw-100 text-truncate" title="Current project of the task" t-esc="task.project_id.name" />
</td>
<td t-if="groupby != 'stage'">
<span class="badge badge-pill badge-info" title="Current stage of the task" t-esc="task.stage_id.name" />
<td t-if="groupby != 'stage'" class="text-right">
<span t-attf-class="badge #{'badge-primary' if task.stage_id.fold else 'badge-light'}" title="Current stage of the task" t-esc="task.stage_id.name"/>
</td>
</tr>
</t>
@@ -4,7 +4,7 @@
<field name="name">project.project.stage.view.tree</field>
<field name="model">project.project.stage</field>
<field name="arch" type="xml">
<tree editable="bottom">
<tree editable="bottom" sample="1">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="mail_template_id" optional="hide" context="{'default_model': 'project.project'}"/>
+75 -20
View File
@@ -80,13 +80,40 @@
</field>
</record>
<record id="view_project_task_pivot" model="ir.ui.view">
<field name="name">project.task.pivot</field>
<field name="model">project.task</field>
<field name="arch" type="xml">
<pivot string="Tasks" sample="1" js_class="project_pivot">
<field name="project_id" type="row"/>
<field name="stage_id" type="col"/>
<field name="color" invisible="1"/>
<field name="sequence" invisible="1"/>
<field name="planned_hours" widget="float_time"/>
<field name="working_hours_close" widget="float_time"/>
<field name="working_hours_open" widget="float_time"/>
</pivot>
</field>
</record>
<record id="view_project_task_pivot_inherit" model="ir.ui.view">
<field name="name">project.task.pivot.inherit</field>
<field name="model">project.task</field>
<field name="mode">primary</field>
<field name="inherit_id" ref="project.view_project_task_pivot"/>
<field name="arch" type="xml">
<xpath expr="/pivot" position="inside">
<field name="user_ids" type="row"/>
</xpath>
</field>
</record>
<record id="act_project_project_2_project_task_all" model="ir.actions.act_window">
<field name="name">Tasks</field>
<field name="res_model">project.task</field>
<field name="view_mode">kanban,tree,form,calendar,pivot,graph,activity</field>
<field name="domain">[('display_project_id', '=', active_id)]</field>
<field name="context">{
'pivot_row_groupby': ['user_ids'],
'default_project_id': active_id,
'show_project_update': True,
}</field>
@@ -102,6 +129,50 @@
</field>
</record>
<!-- Set pivot view and arrange in order -->
<record id="project_task_kanban_action_view" model="ir.actions.act_window.view">
<field name="sequence" eval="10"/>
<field name="view_mode">kanban</field>
<field name="act_window_id" ref="project.act_project_project_2_project_task_all"/>
</record>
<record id="project_task_tree_action_view" model="ir.actions.act_window.view">
<field name="sequence" eval="20"/>
<field name="view_mode">tree</field>
<field name="act_window_id" ref="project.act_project_project_2_project_task_all"/>
</record>
<record id="project_task_form_action_view" model="ir.actions.act_window.view">
<field name="sequence" eval="30"/>
<field name="view_mode">form</field>
<field name="act_window_id" ref="project.act_project_project_2_project_task_all"/>
</record>
<record id="project_all_task_calendar_action_view" model="ir.actions.act_window.view">
<field name="sequence" eval="40"/>
<field name="view_mode">calendar</field>
<field name="act_window_id" ref="project.act_project_project_2_project_task_all"/>
</record>
<record id="project_all_task_pivot_action_view" model="ir.actions.act_window.view">
<field name="sequence" eval="70"/>
<field name="view_mode">pivot</field>
<field name="view_id" ref="view_project_task_pivot_inherit"/>
<field name="act_window_id" ref="act_project_project_2_project_task_all"/>
</record>
<record id="project_all_task_graph_action_view" model="ir.actions.act_window.view">
<field name="sequence" eval="80"/>
<field name="view_mode">graph</field>
<field name="act_window_id" ref="project.act_project_project_2_project_task_all"/>
</record>
<record id="project_all_task_activity_action_view" model="ir.actions.act_window.view">
<field name="sequence" eval="90"/>
<field name="view_mode">activity</field>
<field name="act_window_id" ref="project.act_project_project_2_project_task_all"/>
</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>
@@ -667,7 +738,7 @@
<div class="o_kanban_card_content mw-100">
<div class="o_kanban_primary_left">
<div class="o_primary">
<span class="o_text_overflow"><t t-esc="record.display_name.value"/></span>
<span class="o_text_overflow" t-att-title="record.display_name.value"><t t-esc="record.display_name.value"/></span>
<span class="o_text_overflow text-muted" t-if="record.partner_id.value">
<span class="fa fa-user mr-2" aria-label="Partner" title="Partner"></span><t t-esc="record.partner_id.value"/>
</span>
@@ -968,7 +1039,7 @@
</tree>
</field>
</page>
<page name="recurrence" string="Recurrence" attrs="{'invisible': [('recurring_task', '=', False)]}">
<page name="recurrence" string="Recurrence" attrs="{'invisible': [('recurring_task', '=', False)]}" groups="project.group_project_recurring_tasks">
<group>
<group>
<label for="repeat_interval" />
@@ -1069,7 +1140,7 @@
<form>
<group>
<field name="name" string = "Task Title" placeholder="e.g. Send Invitations"/>
<field name="user_ids" options="{'no_open': True,'no_create': True}" domain="[('share', '=', False), ('active', '=', True)]"
<field name="user_ids" options="{'no_open': True, 'no_quick_create': True}" domain="[('share', '=', False), ('active', '=', True)]"
widget="many2many_tags"/>
<field name="company_id" invisible="1"/>
<field name="parent_id" invisible="1" groups="base.group_no_one"/>
@@ -1256,22 +1327,6 @@
</field>
</record>
<record id="view_project_task_pivot" model="ir.ui.view">
<field name="name">project.task.pivot</field>
<field name="model">project.task</field>
<field name="arch" type="xml">
<pivot string="Tasks" sample="1" js_class="project_pivot">
<field name="project_id" type="row"/>
<field name="stage_id" type="col"/>
<field name="color" invisible="1"/>
<field name="sequence" invisible="1"/>
<field name="planned_hours" widget="float_time"/>
<field name="working_hours_close" widget="float_time"/>
<field name="working_hours_open" widget="float_time"/>
</pivot>
</field>
</record>
<record id="view_project_task_graph" model="ir.ui.view">
<field name="name">project.task.graph</field>
<field name="model">project.task</field>
@@ -21,14 +21,14 @@
<label for="partner_ids" string="Invite People" attrs="{'invisible': [('access_mode', '=', 'read')]}"/>
<label for="partner_ids" attrs="{'invisible': [('access_mode', '=', 'edit')]}"/>
</div>
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to share the project..." nolabel="1"/>
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to share the project..." nolabel="1" context="{'show_email': True}"/>
</group>
<group>
<field name="note" placeholder="Add a note"/>
<field name="note" placeholder="Add a note" nolabel="1"/>
</group>
<footer>
<button string="Send" name="action_send_mail" attrs="{'invisible': [('access_warning', '!=', '')]}" type="object" class="btn-primary" data-hotkey="q"/>
<button string="Cancel" class="btn-default" special="cancel" data-hotkey="z" />
<button string="Discard" class="btn-secondary" special="cancel" data-hotkey="z" />
</footer>
</form>
</field>
+3 -2
View File
@@ -7,10 +7,11 @@ from odoo import fields, models
class ReportProjectTaskUser(models.Model):
_inherit = "report.project.task.user"
sale_line_id = fields.Many2one('sale.order.line', string='Sales Order Item', readonly=True)
sale_order_id = fields.Many2one('sale.order', string='Sales Order', readonly=True)
def _select(self):
return super()._select() + ", t.sale_order_id"
return super()._select() + ", t.sale_line_id as sale_line_id, t.sale_order_id"
def _group_by(self):
return super()._group_by() + ", t.sale_order_id"
return super()._group_by() + ", t.sale_line_id, t.sale_order_id"
@@ -9,6 +9,9 @@
<xpath expr="//field[@name='partner_id']" position="after">
<field name="sale_order_id"/>
</xpath>
<xpath expr="//filter[@name='Customer']" position="after">
<filter string="Sales Order Item" name="sale_line_id" context="{'group_by': 'sale_line_id'}"/>
</xpath>
</field>
</record>
</data>