[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:
@@ -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',
|
||||
|
||||
@@ -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', '>', 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>
|
||||
|
||||
@@ -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 > 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', '>', 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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', '<', 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', '<', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<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"/>
|
||||
<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'}"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user