[IMP] sale_{project,timesheet}: Add SOL stat button to project update

This commit adds a "Sales Order Line" stat button in project updates to easily search among the SOLs of a project
and be able to create new ones.

Task-3278895
This commit is contained in:
Vincent Larcin
2023-07-20 07:58:41 +02:00
committed by Xavier Bol (xbo)
parent b12ebdfd4e
commit 4fd1532936
5 changed files with 136 additions and 2 deletions
+1
View File
@@ -16,6 +16,7 @@ This module allows to generate a project/task from sales orders.
'security/sale_project_security.xml',
'views/product_views.xml',
'views/project_task_views.xml',
'views/sale_order_line_views.xml',
'views/sale_order_views.xml',
'views/sale_project_portal_templates.xml',
'views/project_sharing_views.xml',
+43 -1
View File
@@ -26,6 +26,7 @@ class Project(models.Model):
"It can be modified on each task and timesheet entry individually if necessary.")
sale_order_id = fields.Many2one(string='Sales Order', related='sale_line_id.order_id', help="Sales order to which the project is linked.")
has_any_so_to_invoice = fields.Boolean('Has SO to Invoice', compute='_compute_has_any_so_to_invoice')
sale_order_line_count = fields.Integer(compute='_compute_sale_order_count', groups='sales_team.group_sale_salesman')
sale_order_count = fields.Integer(compute='_compute_sale_order_count', groups='sales_team.group_sale_salesman')
has_any_so_with_nothing_to_invoice = fields.Boolean('Has a SO with an invoice status of No', compute='_compute_has_any_so_with_nothing_to_invoice')
invoice_count = fields.Integer(compute='_compute_invoice_count', groups='account.group_account_readonly')
@@ -94,7 +95,9 @@ class Project(models.Model):
def _compute_sale_order_count(self):
sale_order_items_per_project_id = self._fetch_sale_order_items_per_project_id({'project.task': [('state', 'not in', list(CLOSED_STATES))]})
for project in self:
project.sale_order_count = len(sale_order_items_per_project_id.get(project.id, self.env['sale.order.line']).order_id)
sale_order_lines = sale_order_items_per_project_id.get(project.id, self.env['sale.order.line'])
project.sale_order_line_count = len(sale_order_lines)
project.sale_order_count = len(sale_order_lines.order_id)
def _compute_invoice_count(self):
query = self.env['account.move.line']._search([('move_id.move_type', 'in', ['out_invoice', 'out_refund'])])
@@ -115,6 +118,35 @@ class Project(models.Model):
for project in self:
project.display_sales_stat_buttons = project.allow_billable and project.partner_id
def action_view_sols(self):
self.ensure_one()
all_sale_order_lines = self._fetch_sale_order_items({'project.task': [('state', 'not in', list(CLOSED_STATES))]})
action_window = {
'type': 'ir.actions.act_window',
'res_model': 'sale.order.line',
'name': _("%(name)s's Sales Order Items", name=self.name),
'context': {
'show_sale': True,
'link_to_project': self.id,
'form_view_ref': 'sale_project.sale_order_line_view_form_editable', # Necessary for some logic in the form view
'default_partner_id': self.partner_id.id,
'default_company_id': self.company_id.id,
'default_order_id': self.sale_order_id.id,
},
'views': [(self.env.ref('sale_project.sale_order_line_view_form_editable').id, 'form')],
}
if len(all_sale_order_lines) <= 1:
action_window['res_id'] = all_sale_order_lines.id
else:
action_window.update({
'domain': [('id', 'in', all_sale_order_lines.ids)],
'views': [
(self.env.ref('sale_project.view_order_line_tree_with_create').id, 'tree'),
(self.env.ref('sale_project.sale_order_line_view_form_editable').id, 'form'),
],
})
return action_window
def action_view_sos(self):
self.ensure_one()
all_sale_orders = self._fetch_sale_order_items({'project.task': [('state', 'not in', list(CLOSED_STATES))]}).order_id
@@ -616,6 +648,16 @@ class Project(models.Model):
'show': self_sudo.display_sales_stat_buttons and self_sudo.sale_order_count > 0,
'sequence': 27,
})
if self.user_has_groups('sales_team.group_sale_salesman_all_leads'):
buttons.append({
'icon': 'dollar',
'text': _lt('Sales Order Items'),
'number': self.sale_order_line_count,
'action_type': 'object',
'action': 'action_view_sols',
'show': self.display_sales_stat_buttons,
'sequence': 28,
})
if self.user_has_groups('account.group_account_readonly'):
self_sudo = self.sudo()
buttons.append({
@@ -5,6 +5,7 @@ from collections import defaultdict
from markupsafe import escape
from odoo import api, Command, fields, models, _
from odoo.exceptions import AccessError
from odoo.tools import format_amount
from odoo.tools.sql import column_exists, create_column
@@ -22,6 +23,52 @@ class SaleOrderLine(models.Model):
# used to know if generate a task and/or a project, depending on the product settings
reached_milestones_ids = fields.One2many('project.milestone', 'sale_line_id', string='Reached Milestones', domain=[('is_reached', '=', True)])
def default_get(self, fields):
res = super().default_get(fields)
if self.env.context.get('form_view_ref') == 'sale_project.sale_order_line_view_form_editable':
default_values = {
'name': _("New Sales Order Item"),
}
# If we can't add order lines to the default order, discard it
if 'order_id' in res:
try:
self.env['sale.order'].browse(res['order_id']).check_access_rule('write')
except AccessError:
del res['order_id']
if 'order_id' in fields and 'order_id' not in res:
assert (partner_id := self.env.context.get('default_partner_id'))
project_id = self.env.context.get('link_to_project')
sale_order = None
if project_id:
try:
project_so = self.env['project.project'].browse(project_id).sale_order_id
project_so.check_access_rule('write')
sale_order = project_so
except AccessError:
pass
if not sale_order:
so_create_values = {
'partner_id': partner_id,
'project_ids': [Command.link(project_id)],
}
else:
so_create_values = {
'partner_id': partner_id,
}
if not sale_order:
company_id = self.env.context.get('defaut_company_id', False)
if company_id:
so_create_values['company_id'] = company_id
sale_order = self.env['sale.order'].create(so_create_values)
sale_order.action_confirm()
default_values['order_id'] = sale_order.id
return {**res, **default_values}
return res
@api.depends('product_id.type')
def _compute_product_updatable(self):
super()._compute_product_updatable()
@@ -70,6 +117,13 @@ class SaleOrderLine(models.Model):
if line.task_id and not has_task:
msg_body = escape(_("Task Created (%s): %s")) % (line.product_id.name, line.task_id._get_html_link())
line.order_id.message_post(body=msg_body)
# Set a service SOL on the project, if any is given
if (project_id := self.env.context.get('link_to_project')):
assert (service_line := next((line for line in lines if line.is_service), False))
project = self.env['project.project'].browse(project_id)
if not project.sale_line_id:
project.sale_line_id = service_line
return lines
def write(self, values):
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_order_line_tree_with_create" model="ir.ui.view">
<field name="name">sale.order.line.tree.with.create</field>
<field name="model">sale.order.line</field>
<field name="inherit_id" ref="sale.view_order_line_tree"/>
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<tree position="attributes">
<attribute name="create">true</attribute>
</tree>
</field>
</record>
<record id="sale_order_line_view_form_editable" model="ir.ui.view">
<field name="name">sale.order.line.view.form.editable</field>
<field name="model">sale.order.line</field>
<field name="inherit_id" ref="sale.sale_order_line_view_form_readonly"/>
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<form position="attributes">
<attribute name="edit">true</attribute>
</form>
<field name="product_id" position="attributes">
<attribute name="domain">[('type', '=', 'service')]</attribute>
<attribute name="context">{
'default_detailed_type': 'service',
}</attribute>
</field>
</field>
</record>
</odoo>
+3 -1
View File
@@ -195,7 +195,9 @@ class Project(models.Model):
def _compute_sale_order_count(self):
billable_projects = self.filtered('allow_billable')
super(Project, billable_projects)._compute_sale_order_count()
(self - billable_projects).sale_order_count = 0
non_billable_projects = self - billable_projects
non_billable_projects.sale_order_line_count = 0
non_billable_projects.sale_order_count = 0
@api.constrains('sale_line_id')
def _check_sale_line_type(self):