[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:
Odoo's Mergebot
2021-09-15 12:16:49 +02:00
committed by GitHub
14 changed files with 192 additions and 150 deletions
@@ -39,7 +39,7 @@
<filter string="Tasks in Overtime" name="overtime" domain="[('task_id.overtime', '&gt;', 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>
+46 -10
View File
@@ -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',
+1 -1
View File
@@ -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):
+2 -3
View File
@@ -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', '&lt;', 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 () {},
+11 -1
View File
@@ -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}
+1 -1
View File
@@ -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"/>