[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:
committed by
Xavier Bol (xbo)
parent
b12ebdfd4e
commit
4fd1532936
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user