[FW][MERGE] project: Improve global UX
This PR fixes and improves features introduced in version 15 for services modules. task-2638359 -- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr closes odoo/odoo#76540 Forward-port-of: odoo/odoo#76166 Signed-off-by: LTU-Odoo <IT-Ideas@users.noreply.github.com>
This commit is contained in:
@@ -39,7 +39,7 @@
|
||||
<filter string="Tasks in Overtime" name="overtime" domain="[('task_id.overtime', '>', 0)]"/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='my_tasks']" position="before">
|
||||
<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_id', '!=', uid), ('user_id', '!=', False)]"/>
|
||||
<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)]"/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='my_tasks']" position="after">
|
||||
<filter string="My Team's Tasks" name="my_team_tasks" domain="[('task_id.user_ids.employee_id.parent_id.user_id', '=', uid)]" />
|
||||
@@ -52,7 +52,7 @@
|
||||
<field name="model">report.project.task.user</field>
|
||||
<field name="inherit_id" ref="project.report_project_task_user_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="user_id" position="after">
|
||||
<field name="user_ids" position="after">
|
||||
<field name="hours_effective" optional="hide" widget="float_time"/>
|
||||
<field name="progress" optional="hide" widget="progressbar"/>
|
||||
</field>
|
||||
|
||||
@@ -476,6 +476,18 @@ class Project(models.Model):
|
||||
project.message_subscribe(project.partner_id.ids)
|
||||
if vals.get('privacy_visibility'):
|
||||
self._change_privacy_visibility()
|
||||
if 'name' in vals and self.analytic_account_id:
|
||||
projects_read_group = self.env['project.project'].read_group(
|
||||
[('analytic_account_id', 'in', self.analytic_account_id.ids)],
|
||||
['analytic_account_id'],
|
||||
['analytic_account_id']
|
||||
)
|
||||
analytic_account_to_update = self.env['account.analytic.account'].browse([
|
||||
res['analytic_account_id'][0]
|
||||
for res in projects_read_group
|
||||
if res['analytic_account_id'] and res['analytic_account_id_count'] == 1
|
||||
])
|
||||
analytic_account_to_update.write({'name': self.name})
|
||||
return res
|
||||
|
||||
def action_unlink(self):
|
||||
@@ -628,16 +640,8 @@ class Project(models.Model):
|
||||
}
|
||||
|
||||
def action_view_kanban_project(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'res_model': 'project.project',
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _("Project"),
|
||||
'res_id': self.id,
|
||||
'views': [(self.env.ref('project.edit_project').id, 'form')],
|
||||
'view_mode': 'form',
|
||||
'context': {'form_view_initial_mode': 'readonly'}
|
||||
}
|
||||
# [XBO] TODO: remove me in master
|
||||
return
|
||||
|
||||
# ---------------------------------------------
|
||||
# PROJECT UPDATES
|
||||
@@ -1020,6 +1024,7 @@ class Task(models.Model):
|
||||
dependent_ids = fields.Many2many('project.task', relation="task_dependencies_rel", column1="depends_on_id",
|
||||
column2="task_id", string="Block",
|
||||
domain="[('allow_task_dependencies', '=', True), ('id', '!=', id)]")
|
||||
dependent_tasks_count = fields.Integer(string="Dependent Tasks", compute='_compute_dependent_tasks_count')
|
||||
|
||||
# recurrence fields
|
||||
allow_recurring_tasks = fields.Boolean(related='project_id.allow_recurring_tasks')
|
||||
@@ -1275,6 +1280,21 @@ class Task(models.Model):
|
||||
for task in recurring_tasks:
|
||||
task.recurring_count = tasks_count.get(task.recurrence_id.id, 0)
|
||||
|
||||
@api.depends('dependent_ids')
|
||||
def _compute_dependent_tasks_count(self):
|
||||
tasks_with_dependency = self.filtered('allow_task_dependencies')
|
||||
(self - tasks_with_dependency).dependent_tasks_count = 0
|
||||
if tasks_with_dependency:
|
||||
group_dependent = self.env['project.task'].read_group([
|
||||
('depend_on_ids', 'in', tasks_with_dependency.ids),
|
||||
], ['depend_on_ids'], ['depend_on_ids'])
|
||||
dependent_tasks_count_dict = {
|
||||
group['depend_on_ids'][0]: group['depend_on_ids_count']
|
||||
for group in group_dependent
|
||||
}
|
||||
for task in tasks_with_dependency:
|
||||
task.dependent_tasks_count = dependent_tasks_count_dict.get(task.id, 0)
|
||||
|
||||
@api.depends('partner_id.email')
|
||||
def _compute_partner_email(self):
|
||||
for task in self:
|
||||
@@ -2009,6 +2029,22 @@ class Task(models.Model):
|
||||
'context': self._context
|
||||
}
|
||||
|
||||
def action_dependent_tasks(self):
|
||||
self.ensure_one()
|
||||
action = {
|
||||
'res_model': 'project.task',
|
||||
'type': 'ir.actions.act_window',
|
||||
'context': {**self._context, 'default_depend_on_ids': [Command.link(self.id)]},
|
||||
}
|
||||
if self.dependent_tasks_count == 1:
|
||||
action['view_mode'] = 'form'
|
||||
action['res_id'] = self.dependent_ids.id
|
||||
else:
|
||||
action['name'] = _('Dependent Tasks')
|
||||
action['view_mode'] = 'tree,form,kanban,calendar,pivot,graph,gantt,activity,map'
|
||||
action['domain'] = [('depend_on_ids', '=', self.id)]
|
||||
return action
|
||||
|
||||
def action_recurring_tasks(self):
|
||||
return {
|
||||
'name': 'Tasks in Recurrence',
|
||||
|
||||
@@ -19,7 +19,7 @@ STATUS_COLOR = {
|
||||
class ProjectUpdate(models.Model):
|
||||
_name = 'project.update'
|
||||
_description = 'Project Update'
|
||||
_order = 'create_date desc'
|
||||
_order = 'date desc'
|
||||
_inherit = ['mail.thread.cc', 'mail.activity.mixin']
|
||||
|
||||
def default_get(self, fields):
|
||||
|
||||
@@ -11,7 +11,8 @@ class ReportProjectTaskUser(models.Model):
|
||||
_auto = False
|
||||
|
||||
name = fields.Char(string='Task', readonly=True)
|
||||
user_id = fields.Many2one('res.users', string='Assigned To', readonly=True)
|
||||
user_ids = fields.Many2many('res.users', relation='project_task_user_rel', column1='task_id', column2='user_id',
|
||||
string='Assignees', readonly=True)
|
||||
create_date = fields.Datetime("Create Date", readonly=True)
|
||||
date_assign = fields.Datetime(string='Assignment Date', readonly=True)
|
||||
date_end = fields.Datetime(string='Ending Date', readonly=True)
|
||||
@@ -55,7 +56,6 @@ class ReportProjectTaskUser(models.Model):
|
||||
t.date_end as date_end,
|
||||
t.date_last_stage_update as date_last_stage_update,
|
||||
t.date_deadline as date_deadline,
|
||||
tu.user_id,
|
||||
t.project_id,
|
||||
t.priority,
|
||||
t.name as name,
|
||||
@@ -82,7 +82,6 @@ class ReportProjectTaskUser(models.Model):
|
||||
t.date_end,
|
||||
t.date_deadline,
|
||||
t.date_last_stage_update,
|
||||
tu.user_id,
|
||||
t.project_id,
|
||||
t.priority,
|
||||
t.name,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Tasks Analysis" sample="1">
|
||||
<field name="project_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="user_ids"/>
|
||||
<field name="nbr" invisible="1"/>
|
||||
</graph>
|
||||
</field>
|
||||
@@ -31,7 +31,7 @@
|
||||
<field name="name"/>
|
||||
<field name="partner_id" optional="hide"/>
|
||||
<field name="project_id" optional="show"/>
|
||||
<field name="user_id" optional="show" widget="many2one_avatar_user"/>
|
||||
<field name="user_ids" optional="show" widget="many2many_avatar_user"/>
|
||||
<field name="stage_id" optional="show"/>
|
||||
<field name="company_id" optional="show" groups="base.group_multi_company"/>
|
||||
</tree>
|
||||
@@ -46,7 +46,7 @@
|
||||
<field name="project_id"/>
|
||||
<field name="name" />
|
||||
<field name="stage_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="user_ids"/>
|
||||
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
|
||||
<field name="name" string="Tags" filter_domain="[('task_id.tag_ids', 'ilike', self)]"/>
|
||||
<field name="date_assign"/>
|
||||
@@ -59,7 +59,7 @@
|
||||
<filter string="Starred" name="starred" domain="[('task_id.priority', 'in', [0, 1])]"/>
|
||||
<separator/>
|
||||
<filter string="Tasks Late" name="late" domain="[('task_id.date_deadline', '<', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<filter string="Unassigned Tasks" name="unassigned" domain="[('user_id', '=', False)]"/>
|
||||
<filter string="Unassigned Tasks" name="unassigned" domain="[('user_ids', '=', False)]"/>
|
||||
<filter string="Open tasks" name="open_tasks" domain="[('stage_id.fold', '=', False), ('stage_id.is_closed', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter name="filter_date_deadline" date="date_deadline"/>
|
||||
@@ -72,7 +72,7 @@
|
||||
<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="Assigned to" name="User" context="{'group_by': 'user_id'}"/>
|
||||
<filter string="Assigned to" name="User" context="{'group_by': 'user_ids'}"/>
|
||||
<filter string="Customer" name="Customer" context="{'group_by': 'partner_id'}"/>
|
||||
<filter string="Deadline" name="deadline" context="{'group_by': 'date_deadline'}"/>
|
||||
</group>
|
||||
|
||||
@@ -18,7 +18,8 @@ class ReportProjectTaskBurndownChart(models.Model):
|
||||
display_project_id = fields.Many2one('project.project', readonly=True)
|
||||
stage_id = fields.Many2one('project.task.type', readonly=True)
|
||||
date = fields.Datetime('Date', readonly=True)
|
||||
user_id = fields.Many2one('res.users', string='Assigned to', readonly=True)
|
||||
user_ids = fields.Many2many('res.users', relation='project_task_user_rel', column1='task_id', column2='user_id',
|
||||
string='Assignees', readonly=True)
|
||||
date_assign = fields.Datetime(string='Assignment Date', readonly=True)
|
||||
date_deadline = fields.Date(string='Deadline', readonly=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True)
|
||||
@@ -59,7 +60,6 @@ WITH all_moves_stage_task AS (
|
||||
COALESCE(LAG(mm.date) OVER (PARTITION BY mm.res_id ORDER BY mm.id), pt.create_date) as date_begin,
|
||||
mm.date as date_end,
|
||||
mtv.old_value_integer as stage_id,
|
||||
usr_rel.user_id,
|
||||
pt.date_assign,
|
||||
pt.date_deadline,
|
||||
pt.partner_id
|
||||
@@ -71,7 +71,6 @@ WITH all_moves_stage_task AS (
|
||||
JOIN ir_model_fields imf ON mtv.field = imf.id
|
||||
AND imf.model = 'project.task'
|
||||
AND imf.name = 'stage_id'
|
||||
JOIN project_task_user_rel usr_rel ON pt.id=usr_rel.task_id
|
||||
WHERE pt.active
|
||||
|
||||
--We compute the last reached stage
|
||||
@@ -83,7 +82,6 @@ WITH all_moves_stage_task AS (
|
||||
COALESCE(md.date, pt.create_date) as date_begin,
|
||||
(CURRENT_DATE + interval '1 month')::date as date_end,
|
||||
pt.stage_id,
|
||||
usr_rel.user_id,
|
||||
pt.date_assign,
|
||||
pt.date_deadline,
|
||||
pt.partner_id
|
||||
@@ -99,7 +97,6 @@ WITH all_moves_stage_task AS (
|
||||
AND mm.model = 'project.task'
|
||||
ORDER BY mm.id DESC
|
||||
FETCH FIRST ROW ONLY) md ON TRUE
|
||||
JOIN project_task_user_rel usr_rel ON pt.id=usr_rel.task_id
|
||||
WHERE pt.active
|
||||
)
|
||||
SELECT (task_id*10^7 + 10^6 + to_char(d, 'YYMMDD')::integer)::bigint as id,
|
||||
@@ -108,7 +105,6 @@ SELECT (task_id*10^7 + 10^6 + to_char(d, 'YYMMDD')::integer)::bigint as id,
|
||||
display_project_id,
|
||||
stage_id,
|
||||
d as date,
|
||||
user_id,
|
||||
date_assign,
|
||||
date_deadline,
|
||||
partner_id,
|
||||
@@ -125,7 +121,6 @@ SELECT (task_id*10^7 + 2*10^6 + to_char(d, 'YYMMDD')::integer)::bigint as id,
|
||||
display_project_id,
|
||||
stage_id,
|
||||
date_trunc('week', d) as date,
|
||||
user_id,
|
||||
date_assign,
|
||||
date_deadline,
|
||||
partner_id,
|
||||
@@ -144,7 +139,6 @@ SELECT (task_id*10^7 + 3*10^6 + to_char(d, 'YYMMDD')::integer)::bigint as id,
|
||||
display_project_id,
|
||||
stage_id,
|
||||
date_trunc('month', d) as date,
|
||||
user_id,
|
||||
date_assign,
|
||||
date_deadline,
|
||||
partner_id,
|
||||
@@ -163,7 +157,6 @@ SELECT (task_id*10^7 + 4*10^6 + to_char(d, 'YYMMDD')::integer)::bigint as id,
|
||||
display_project_id,
|
||||
stage_id,
|
||||
date_trunc('quarter', d) as date,
|
||||
user_id,
|
||||
date_assign,
|
||||
date_deadline,
|
||||
partner_id,
|
||||
@@ -182,7 +175,6 @@ SELECT (task_id*10^7 + 5*10^6 + to_char(d, 'YYMMDD')::integer)::bigint as id,
|
||||
display_project_id,
|
||||
stage_id,
|
||||
date_trunc('year', d) as date,
|
||||
user_id,
|
||||
date_assign,
|
||||
date_deadline,
|
||||
partner_id,
|
||||
|
||||
@@ -7,12 +7,10 @@
|
||||
<field name="arch" type="xml">
|
||||
<search string="Burndown Chart">
|
||||
<field name="project_id" />
|
||||
<field name="user_id" />
|
||||
<field name="date_assign"/>
|
||||
<field name="date_deadline"/>
|
||||
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
|
||||
<field name="stage_id" />
|
||||
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter name="filter_date" date="date" string="Date" />
|
||||
<filter name="filter_date_deadline" date="date_deadline"/>
|
||||
@@ -23,7 +21,6 @@
|
||||
<filter string="Date" name="date" context="{'group_by': 'date'}" />
|
||||
<filter string="Stage" name="stage" context="{'group_by': 'stage_id'}" />
|
||||
<filter string="Project" name="project" context="{'group_by': 'project_id'}" />
|
||||
<filter string="Assigned To" name="user" context="{'group_by': 'user_id'}" />
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
|
||||
@@ -72,91 +72,9 @@ tour.register('project_update_tour', {
|
||||
trigger: ".o_kanban_record .oe_kanban_content",
|
||||
extra_trigger: '.o_kanban_project_tasks',
|
||||
run: "drag_and_drop .o_kanban_group:eq(1) ",
|
||||
}, {
|
||||
trigger: '.o_back_button',
|
||||
content: 'Go back to edit the project created',
|
||||
}, {
|
||||
// an invisible element cannot be used as a trigger so this small hack is mandatory for the next step
|
||||
trigger: '.o_kanban_record:contains("New Project")',
|
||||
run: function () {
|
||||
$('.o_kanban_record:contains("New Project") .o_dropdown_kanban').css('visibility', 'visible');
|
||||
}
|
||||
}, {
|
||||
trigger: '.oe_kanban_global_click :contains("New Project") .o_dropdown_kanban',
|
||||
content: 'Open the project dropdown'
|
||||
}, {
|
||||
trigger: '.o_kanban_record:contains("New Project") .dropdown-menu a:contains("See More")',
|
||||
content: 'Start editing the project',
|
||||
}, {
|
||||
trigger: '.nav-item:contains("Invoicing") .nav-link',
|
||||
content: 'Start the assignement of a default sale order id to the project'
|
||||
}, {
|
||||
trigger: '.o_form_button_edit',
|
||||
content: 'Start editing the project'
|
||||
}, {
|
||||
trigger: 'div[name="sale_line_id"]',
|
||||
content: 'Select the first sale order of the list',
|
||||
run: function (actions) {
|
||||
actions.text('Prepaid', this.$anchor.find('input'));
|
||||
},
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete > li:first-child > a',
|
||||
content: 'Select the first item on the autocomplete dropdown'
|
||||
}, {
|
||||
trigger: '.o_form_button_save',
|
||||
content: 'Save the modifications'
|
||||
}, {
|
||||
trigger: '.o_back_button',
|
||||
content: 'Go back to the kanban view the project created',
|
||||
}, {
|
||||
trigger: '.oe_kanban_global_click :contains("New Project")',
|
||||
content: 'Open the project'
|
||||
}, {
|
||||
trigger: ".o_project_updates_breadcrumb",
|
||||
content: 'Open Updates'
|
||||
}, {
|
||||
trigger: ".oe_button_box .o_stat_text:contains('Sales Order')",
|
||||
run: function () {}
|
||||
}, {
|
||||
trigger: ".o_rightpanel_title:eq(0):contains('Sold')",
|
||||
content: 'Check the user sees Sold section',
|
||||
run: function () {}
|
||||
}, {
|
||||
trigger: ".o_rightpanel_left_text:eq(0):contains('Prepaid Hours')",
|
||||
content: 'Check the user sees Sold subsection',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_title:eq(1):contains('Total Sold')",
|
||||
content: 'Check the user sees Total Sold section',
|
||||
run: function () {}
|
||||
}, {
|
||||
trigger: ".o_rightpanel_header:eq(1) .o_rightpanel_right_col:contains('Hours')",
|
||||
content: 'Check the user sees Total Sold hours title',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section:eq(2) .o_rightpanel_data_row:contains('Effective')",
|
||||
content: 'Check the user sees Total Sold subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section:eq(2) .o_rightpanel_data_row:contains('Remaining')",
|
||||
content: 'Check the user sees Total Sold subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_title:eq(2):contains('Profitability')",
|
||||
content: 'Check the user sees Profitability section',
|
||||
run: function () {}
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section:eq(3) .o_rightpanel_data_row:contains('Revenues')",
|
||||
content: 'Check the user sees Profitability subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section:eq(3) .o_rightpanel_data_row:contains('Costs')",
|
||||
content: 'Check the user sees Profitability subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section:eq(3) .o_rightpanel_data_row:contains('Margin')",
|
||||
content: 'Check the user sees Profitability subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_add_milestone a",
|
||||
content: "Add a first milestone"
|
||||
@@ -205,15 +123,6 @@ tour.register('project_update_tour', {
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] h1:contains('Activities')",
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] h3:contains('Sold')",
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] td:contains('Customer Care')",
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] h3:contains('Profitability')",
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] h3:contains('Milestones')",
|
||||
run: function () {},
|
||||
|
||||
@@ -676,7 +676,8 @@
|
||||
</div>
|
||||
<div role="menuitem" class="col-4">
|
||||
<a t-if="record.privacy_visibility.raw_value == 'portal'" class="dropdown-item" role="menuitem" name="%(project.project_share_wizard_action)d" type="action">Share</a>
|
||||
<a class="dropdown-item" role="menuitem" type="object" name="action_view_kanban_project">See More</a>
|
||||
<!-- [XBO] TODO: remove the name attribute in this a tag in master -->
|
||||
<a class="dropdown-item" role="menuitem" type="edit" name="action_view_kanban_project">Edit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -806,6 +807,15 @@
|
||||
<span class="o_stat_text">in Recurrence</span>
|
||||
</div>
|
||||
</button>
|
||||
<button name="action_dependent_tasks" type="object" attrs="{'invisible': [('dependent_tasks_count', '=', 0)]}" class="oe_stat_button" icon="fa-tasks" groups="project.group_project_task_dependencies">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_text">Blocking</span>
|
||||
<span class="o_stat_value ">
|
||||
<field name="dependent_tasks_count" widget="statinfo" nolabel="1" />
|
||||
Tasks
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<div class="oe_title pr-0">
|
||||
|
||||
@@ -58,7 +58,7 @@ class ProjectTaskTypeDelete(models.TransientModel):
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("project.action_view_task")
|
||||
action['domain'] = [('project_id', '=', project_id)]
|
||||
action['context'] = str({
|
||||
'pivot_row_groupby': ['user_id'],
|
||||
'pivot_row_groupby': ['user_ids'],
|
||||
'default_project_id': project_id,
|
||||
})
|
||||
elif self.env.context.get('stage_view'):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
from odoo import http, _
|
||||
from odoo import Command, http, _
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class ProjectClient(http.Controller):
|
||||
'partner_id': partner_id,
|
||||
'description': email_body,
|
||||
'project_id': project_id,
|
||||
'user_id': request.env.uid,
|
||||
'user_ids': [Command.link(request.env.uid)],
|
||||
})
|
||||
|
||||
return {'task_id': record.id, 'name': record.name}
|
||||
|
||||
@@ -27,7 +27,7 @@ class ProductTemplate(models.Model):
|
||||
], ondelete={'timesheet': 'set default'})
|
||||
# override domain
|
||||
project_id = fields.Many2one(domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('pricing_type', '=', 'task_rate'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]")
|
||||
project_template_id = fields.Many2one(domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('pricing_type', '!=', 'task_rate'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]")
|
||||
project_template_id = fields.Many2one(domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]")
|
||||
service_upsell_threshold = fields.Float('Threshold', default=1, help="Percentage of time delivered compared to the prepaid amount that must be reached for the upselling opportunity activity to be triggered.")
|
||||
service_upsell_threshold_ratio = fields.Char(compute='_compute_service_upsell_threshold_ratio')
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ tour.register('sale_timesheet_tour', {
|
||||
run: function (actions) {
|
||||
actions.text('Project for Freeman', this.$anchor);
|
||||
},
|
||||
}, {
|
||||
}, {
|
||||
trigger: 'button[name="action_view_tasks"]',
|
||||
content: 'Click on Create button to create and enter to this newest project.',
|
||||
run: 'click',
|
||||
@@ -178,13 +178,6 @@ tour.register('sale_timesheet_tour', {
|
||||
actions.click(this.$anchor);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: 'div.o_notebook_headers',
|
||||
content: 'Click on Invoicing tab to configure the invoicing of this project.',
|
||||
run: function (actions) {
|
||||
const notebookId = $('div[name="sale_line_id"]').closest("div.tab-pane").attr('id');
|
||||
actions.click(this.$anchor.find(`a[data-toggle="tab"][href="#${notebookId}"]`));
|
||||
},
|
||||
}, {
|
||||
trigger: 'div[name="partner_id"]',
|
||||
content: Markup('Add the customer for this project to select an SO and SOL for this customer <i>(e.g. Brandon Freeman)</i>.'),
|
||||
@@ -195,6 +188,13 @@ tour.register('sale_timesheet_tour', {
|
||||
trigger: 'ul.o_partner_autocomplete_dropdown > li:first-child > a:contains(Freeman)',
|
||||
content: 'Select the customer in the autocomplete dropdown',
|
||||
run: 'click',
|
||||
}, {
|
||||
trigger: 'div.o_notebook_headers',
|
||||
content: 'Click on Invoicing tab to configure the invoicing of this project.',
|
||||
run: function (actions) {
|
||||
const notebookId = $('div[name="sale_line_id"]').closest("div.tab-pane").attr('id');
|
||||
actions.click(this.$anchor.find(`a[data-toggle="tab"][href="#${notebookId}"]`));
|
||||
},
|
||||
}, {
|
||||
trigger: 'div[name="sale_line_id"]',
|
||||
content: 'Select a Sales Order Item as Default Sales Order Item for each task in this project.',
|
||||
@@ -239,36 +239,135 @@ tour.register('sale_timesheet_tour', {
|
||||
content: 'Save Project',
|
||||
}, {
|
||||
trigger: 'li[data-menu-xmlid="project.menu_projects"]',
|
||||
content: 'Select Projects'
|
||||
content: 'Select Projects',
|
||||
}, {
|
||||
// an invisible element cannot be used as a trigger so this small hack is mandatory for the next step
|
||||
trigger: 'div.o_kanban_primary_left :contains("Project for Freeman")',
|
||||
content: 'Select Project for Freeman'
|
||||
content: 'Open the project dropdown',
|
||||
run: function () {
|
||||
$('.o_kanban_record:contains("Project for Freeman") .o_dropdown_kanban').css('visibility', 'visible');
|
||||
},
|
||||
}, {
|
||||
trigger: '.oe_kanban_global_click :contains("Project for Freeman") .o_dropdown_kanban',
|
||||
content: 'Open the project dropdown',
|
||||
}, {
|
||||
trigger: '.o_kanban_record:contains("Project for Freeman") .dropdown-menu a:contains("Edit")',
|
||||
content: 'Start editing the project',
|
||||
}, {
|
||||
trigger: 'div.o_notebook_headers',
|
||||
content: 'Click on Invoicing tab to configure the invoicing of this project.',
|
||||
run: function (actions) {
|
||||
const notebookId = $('div[name="sale_line_id"]').closest("div.tab-pane").attr('id');
|
||||
actions.click(this.$anchor.find(`a[data-toggle="tab"][href="#${notebookId}"]`));
|
||||
},
|
||||
}, {
|
||||
trigger: 'div[name="sale_line_id"]',
|
||||
content: 'Select the first sale order of the list',
|
||||
run: function (actions) {
|
||||
actions.text('Prepaid', this.$anchor.find('input'));
|
||||
},
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete > li:first-child > a',
|
||||
content: 'Select the first item on the autocomplete dropdown',
|
||||
}, {
|
||||
trigger: '.o_form_button_save',
|
||||
content: 'Save the modifications',
|
||||
}, {
|
||||
trigger: '.o_back_button',
|
||||
content: 'Go back to the kanban view the project created',
|
||||
}, {
|
||||
trigger: '.oe_kanban_global_click :contains("Project for Freeman")',
|
||||
content: 'Open the project',
|
||||
}, {
|
||||
trigger: ".o_project_updates_breadcrumb",
|
||||
content: 'Open Updates'
|
||||
content: 'Open Updates',
|
||||
}, {
|
||||
trigger: ".o_rightpanel_title:eq(0):contains('Sold')",
|
||||
content: 'Check the user sees Sold section'
|
||||
content: 'Check the user sees Sold section',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_left_text:eq(0):contains('Prepaid Hours')",
|
||||
content: 'Check the user sees Sold subsection',
|
||||
run: function () {},
|
||||
}, {
|
||||
}, {
|
||||
trigger: ".o_rightpanel_title:eq(1):contains('Total Sold')",
|
||||
content: 'Check the user sees Total Sold section'
|
||||
content: 'Check the user sees Total Sold section',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".oe_button_box .o_stat_text:contains('Sales Order')",
|
||||
content: 'Check the user sees Sales Order Stat Button',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_header:eq(1) .o_rightpanel_right_col:contains('Hours')",
|
||||
content: 'Check the user sees Hours title',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section:eq(2) .o_rightpanel_data_row:contains('Effective')",
|
||||
content: 'Check the user sees Effective subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section:eq(2) .o_rightpanel_data_row:contains('Remaining')",
|
||||
content: 'Check the user sees Remaining subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_title:eq(2):contains('Profitability')",
|
||||
content: 'Check the user sees Profitability section'
|
||||
content: 'Check the user sees Profitability section',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section:eq(3) .o_rightpanel_data_row:contains('Revenues')",
|
||||
content: 'Check the user sees Profitability subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section:eq(3) .o_rightpanel_data_row:contains('Costs')",
|
||||
content: 'Check the user sees Profitability subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section:eq(3) .o_rightpanel_data_row:contains('Margin')",
|
||||
content: 'Check the user sees Profitability subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_title:eq(3):contains('Milestones')",
|
||||
content: 'Check the user sees Milestones section'
|
||||
content: 'Check the user sees Milestones section',
|
||||
}, {
|
||||
trigger: ".o-kanban-button-new"
|
||||
trigger: ".o_add_milestone a",
|
||||
content: "Add a first milestone",
|
||||
}, {
|
||||
trigger: "input.o_field_widget[name=name]",
|
||||
run: 'text New update'
|
||||
content: "Edit new Milestone",
|
||||
run: 'text New milestone',
|
||||
}, {
|
||||
trigger: ".o_form_button_save"
|
||||
trigger: "input.datetimepicker-input[name=deadline]",
|
||||
content: "Edit new Milestone",
|
||||
run: 'text 12/12/2099',
|
||||
}, {
|
||||
trigger: ".modal-footer button",
|
||||
content: "Save new Milestone",
|
||||
}, {
|
||||
trigger: ".o-kanban-button-new",
|
||||
content: "Create new Project Update",
|
||||
}, {
|
||||
trigger: "input.o_field_widget[name=name]",
|
||||
content: "Give a name to Project Update",
|
||||
run: 'text New update',
|
||||
}, {
|
||||
trigger: ".o_form_button_save",
|
||||
content: "Save Project Update",
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] h3:contains('Sold')",
|
||||
content: "Sold title must be in description in description",
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] td:contains('Prepaid Hours')",
|
||||
content: "Prepaid Hours title must be in description",
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] h3:contains('Profitability')",
|
||||
content: "Profitability title must be in description",
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] h3:contains('Milestones')",
|
||||
content: "Milestones title must be in description",
|
||||
run: function () {},
|
||||
}]);
|
||||
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<button name="action_make_billable" string="Create Sales Order" type="object" attrs="{'invisible': [('display_create_order', '=', False)]}" groups="sales_team.group_sale_salesman"/>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='settings']" position="after">
|
||||
<page name="billing_employee_rate" string="Invoicing" attrs="{'invisible': [('allow_billable', '=', False)]}">
|
||||
<page name="billing_employee_rate" string="Invoicing" attrs="{'invisible': ['|', ('allow_billable', '=', False), ('partner_id', '=', False)]}">
|
||||
<group>
|
||||
<group>
|
||||
<field name="display_create_order" invisible="1"/>
|
||||
|
||||
Reference in New Issue
Block a user