[IMP] sale,sale_project: show Project Overview

The project overview statbutton will only be shown when there's a proper
Project Overview to display (when there are timesheets, etc.)

closes odoo/odoo#57698

Taskid: 2257764
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Kevin Baptiste
2020-12-02 13:40:01 +00:00
parent 8830525176
commit eb51bdb5ad
8 changed files with 71 additions and 14 deletions
+15
View File
@@ -810,6 +810,21 @@
</field>
</record>
<record id="action_sale_order_form_view" model="ir.actions.act_window">
<field name="name">Sales Order</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.order</field>
<field name="view_mode">form,tree,kanban,calendar,pivot,graph,activity</field>
<field name="search_view_id" ref="sale_order_view_search_inherit_sale"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new quotation, the first step of a new sale!
</p><p>
Once the quotation is confirmed, it becomes a sales order.<br/> You will be able to create an invoice and collect the payment.
</p>
</field>
</record>
<record id="sale_order_action_view_order_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
+8
View File
@@ -16,6 +16,7 @@ class Project(models.Model):
help="Sales order item to which the project is linked. Link the timesheet entry to the sales order item defined on the project. "
"Only applies on tasks without sale order item defined, and if the employee is not in the 'Employee/Sales Order Item Mapping' of the project.")
sale_order_id = fields.Many2one('sale.order', 'Sales Order', domain="[('partner_id', '=', partner_id)]", copy=False, help="Sales order to which the project is linked.")
project_overview = fields.Boolean('Show Project Overview', compute='_compute_project_overview')
_sql_constraints = [
('sale_order_required_if_sale_line', "CHECK((sale_line_id IS NOT NULL AND sale_order_id IS NOT NULL) OR (sale_line_id IS NULL))", 'The project should be linked to a sale order to select a sale order item.'),
@@ -27,6 +28,13 @@ class Project(models.Model):
defaults['sale_line_id'] = False
return defaults
@api.depends('analytic_account_id')
def _compute_project_overview(self):
overview = self.env['project.project']
if self.user_has_groups('analytic.group_analytic_accounting'):
overview = self.filtered(lambda p: p.analytic_account_id)
overview.project_overview = True
(self - overview).project_overview = False
class ProjectTask(models.Model):
_inherit = "project.task"
+16 -1
View File
@@ -17,6 +17,8 @@ class SaleOrder(models.Model):
'project.project', 'Project', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
help='Select a non billable project on which tasks can be created.')
project_ids = fields.Many2many('project.project', compute="_compute_project_ids", string='Projects', copy=False, groups="project.group_project_user", help="Projects used in this sales order.")
project_overview = fields.Boolean('Show Project Overview', compute='_compute_project_overview')
project_count = fields.Integer(string='Number of Projects', compute='_compute_project_ids', groups='project.group_project_user')
@api.depends('order_line.product_id.project_id')
def _compute_tasks_ids(self):
@@ -33,6 +35,12 @@ class SaleOrder(models.Model):
service_tracking == 'task_in_project' for service_tracking in order.order_line.mapped('product_id.service_tracking')
)
@api.depends('project_ids')
def _compute_project_overview(self):
for order in self:
billable_projects = order.project_ids.filtered('sale_line_id')
order.project_overview = len(order.project_ids) == 1 and len(billable_projects) == 1 and billable_projects.project_overview
@api.depends('order_line.product_id', 'order_line.project_id')
def _compute_project_ids(self):
for order in self:
@@ -40,6 +48,7 @@ class SaleOrder(models.Model):
projects |= order.order_line.mapped('project_id')
projects |= order.project_id
order.project_ids = projects
order.project_count = len(projects)
@api.onchange('project_id')
def _onchange_project_id(self):
@@ -92,16 +101,22 @@ class SaleOrder(models.Model):
def action_view_project_ids(self):
self.ensure_one()
if len(self.project_ids) == 1 and self.project_overview:
return self.project_id.action_view_account_analytic_line()
view_form_id = self.env.ref('project.edit_project').id
view_kanban_id = self.env.ref('project.view_project_kanban').id
action = {
'type': 'ir.actions.act_window',
'domain': [('id', 'in', self.project_ids.ids)],
'views': [(view_kanban_id, 'kanban'), (view_form_id, 'form')],
'view_mode': 'kanban,form',
'name': _('Projects'),
'res_model': 'project.project',
}
if len(self.project_ids) == 1:
action.update({'views': [(view_form_id, 'form')], 'res_id': self.project_ids.id})
else:
action['views'] = [(view_kanban_id, 'kanban'), (view_form_id, 'form')]
return action
def write(self, values):
@@ -10,9 +10,12 @@
<button type="object" name="action_view_task" class="oe_stat_button" icon="fa-tasks" attrs="{'invisible': [('tasks_count', '=', 0)]}" groups="project.group_project_user">
<field name="tasks_count" widget="statinfo" string="Tasks"/>
</button>
<button type="object" name="action_view_project_ids" class="oe_stat_button" icon="fa-puzzle-piece" string="Project" attrs="{'invisible': [('project_ids', '=', [])]}" groups="project.group_project_manager">
<button type="object" name="action_view_project_ids" class="oe_stat_button" icon="fa-puzzle-piece" attrs="{'invisible': ['|', ('project_ids', '=', []), ('project_overview', '=', True)]}" groups="project.group_project_manager">
<field name="project_ids" invisible="1"/>
<field name="project_overview" invisible="1"/>
<field name="project_count" widget="statinfo" string="Projects"/>
</button>
<button type="object" name="action_view_project_ids" class="oe_stat_button" icon="fa-puzzle-piece" string="Project Overview" attrs="{'invisible': ['|', ('project_ids', '=', []), ('project_overview', '=', False)]}" groups="project.group_project_manager"/>
</xpath>
<xpath expr="//field[@name='analytic_account_id']" position="after">
<field name="visible_project" invisible="1"/>
+6
View File
@@ -92,6 +92,12 @@ class Project(models.Model):
(self - projects).warning_employee_rate = False
@api.depends('analytic_account_id', 'allow_billable', 'allow_timesheets')
def _compute_project_overview(self):
super()._compute_project_overview()
for project in self.filtered(lambda p: not p.project_overview):
project.project_overview = project.allow_billable or project.allow_timesheets
@api.constrains('sale_line_id', 'pricing_type')
def _check_sale_line_type(self):
for project in self:
@@ -498,15 +498,24 @@ class Project(models.Model):
sale_orders = self.mapped('sale_line_id.order_id') | self.env['sale.order'].browse(task_so_ids)
if sale_orders:
so_action = dict(
context={'create': False, 'edit': False, 'delete': False},
domain=[('id', 'in', sale_orders.ids)],
)
if len(sale_orders) == 1:
so_action.update({
'action': self.env.ref('sale.action_sale_order_form_view').sudo(),
'res_id': sale_orders.id,
})
else:
so_action['action'] = self.env.ref('sale.action_orders').sudo()
stat_buttons.append({
'name': _('Sales Orders'),
'count': len(sale_orders),
'icon': 'fa fa-dollar',
'action': _to_action_data(
action=self.env.ref('sale.action_orders').sudo(),
domain=[('id', 'in', sale_orders.ids)],
context={'create': False, 'edit': False, 'delete': False}
)
'action': _to_action_data(**so_action),
})
invoice_ids = self.env['sale.order'].search_read([('id', 'in', sale_orders.ids)], ['invoice_ids'])
@@ -533,6 +542,10 @@ class Project(models.Model):
else:
timesheet_label = [_('Hours'), _('Recorded')]
default_project_ctx = {}
if len(self) == 1:
default_project_ctx = {'default_project_id': self.id}
stat_buttons.append({
'name': timesheet_label,
'count': sum(self.mapped('total_timesheet_time')),
@@ -541,6 +554,7 @@ class Project(models.Model):
'account.analytic.line',
domain=[('project_id', 'in', self.ids)],
views=[(ts_tree.id, 'list'), (ts_form.id, 'form')],
context=default_project_ctx,
)
})
+3 -4
View File
@@ -38,14 +38,13 @@ class SaleOrder(models.Model):
total_time += timesheet.unit_amount * timesheet.product_uom_id.factor_inv
# Now convert to the proper unit of measure
total_time *= sale_order.timesheet_encode_uom_id.factor
sale_order.timesheet_total_duration = total_time
sale_order.timesheet_total_duration = round(total_time)
def action_view_project_ids(self):
self.ensure_one()
# redirect to form or kanban view
billable_projects = self.project_ids.filtered(lambda project: project.sale_line_id)
if len(billable_projects) == 1 and self.env.user.has_group('project.group_project_manager'):
action = billable_projects[0].action_view_timesheet_plan()
if len(self.project_ids) == 1 and self.project_ids.project_overview and self.env.user.has_group('project.group_project_manager'):
action = self.project_ids.action_view_timesheet_plan()
else:
action = super().action_view_project_ids()
return action
@@ -6,9 +6,6 @@
<field name="inherit_id" ref="sale_project.view_order_form_inherit_sale_project"/>
<field name="arch" type="xml">
<data>
<xpath expr="//button[@name='action_view_project_ids']" position="attributes">
<attribute name="string">Project Overview</attribute>
</xpath>
<xpath expr="//button[@name='action_view_invoice']" position="before">
<field name="timesheet_count" invisible="1" />
<button type="object"