[ADD] sale_service: add additional information in SOL display name

- Impacted modules:
    - sale_service (new module - installed only if sale
    AND (project OR planning) are installed)
    - sale
    - sale_project

- Description: When looking for a SOL in services apps, the displayed
name is of the form "SO ref - SOL name - unit price (partner
ref/name)". The presence of some of those elements may vary:
    - unit price is only displayed if it allows to clarify an
    ambiguity (i.e. two SOL with identical name but different unit
    price in the same SO).
    - partner name is only used if the partner ref does not exist.
    - partner name/ref is hidden in the name of planning slots to avoid
    duplicates.

- Implementation: The behavior currently existing in sale needed to be
extended for services apps. This has been done with the following
changes:
    - sale:
        - sale.order.line: Current behavior for the name_get (display
        partner ref) has been extended (display parnter name if has no
        ref) and split a additional method that is extended in a new
        module called..
    - .. sale_service:
        - sale.order.line: The additional behavior is extended to
        display the unit price of the SOL when it avoids ambiguity. A
        context parameter is used to keep this behavior inside
        services apps.

task-3052812

Part-of: odoo/odoo#105470
This commit is contained in:
Hugo Carlier (Huca)
2023-01-16 15:43:00 +01:00
parent 06f19bdc82
commit 47db8b5dcc
7 changed files with 89 additions and 47 deletions
+12 -2
View File
@@ -1115,12 +1115,22 @@ class SaleOrderLine(models.Model):
#=== CORE METHODS OVERRIDES ===#
def _additional_name_per_id(self):
return {
so_line.id:
'(%s)' % (so_line.order_partner_id.ref or so_line.order_partner_id.name)
for so_line in self
if so_line.order_partner_id.ref or so_line.order_partner_id.name
}
def name_get(self):
result = []
name_per_id = self._additional_name_per_id()
for so_line in self.sudo():
name = '%s - %s' % (so_line.order_id.name, so_line.name and so_line.name.split('\n')[0] or so_line.product_id.name)
if so_line.order_partner_id.ref:
name = '%s (%s)' % (name, so_line.order_partner_id.ref)
additional_name = name_per_id.get(so_line.id)
if additional_name:
name = '%s %s' % (name, additional_name)
result.append((so_line.id, name))
return result
+2 -2
View File
@@ -9,7 +9,8 @@ Allows to create task from your sales order
This module allows to generate a project/task from sales orders.
""",
'category': 'Hidden',
'depends': ['sale_management', 'project'],
'depends': ['sale_management', 'sale_service', 'project'],
'auto_install': ['sale_management', 'project'],
'data': [
'security/ir.model.access.csv',
'security/sale_project_security.xml',
@@ -25,6 +26,5 @@ This module allows to generate a project/task from sales orders.
'sale_project/static/src/components/project_right_side_panel/**/*',
],
},
'auto_install': True,
'license': 'LGPL-3',
}
@@ -19,36 +19,8 @@ class SaleOrderLine(models.Model):
'project.task', 'Generated Task',
index=True, copy=False)
# used to know if generate a task and/or a project, depending on the product settings
is_service = fields.Boolean("Is a Service", compute='_compute_is_service', store=True, compute_sudo=True)
reached_milestones_ids = fields.One2many('project.milestone', 'sale_line_id', string='Reached Milestones', domain=[('is_reached', '=', True)])
def name_get(self):
res = super().name_get()
with_price_unit = self._context.get('with_price_unit', self._context.get('is_timesheet'))
if with_price_unit:
names = dict(res)
result = []
sols_by_so_dict = defaultdict(lambda: self.env[self._name]) # key: (sale_order_id, product_id), value: sale order line
for line in self:
sols_by_so_dict[line.order_id.id, line.product_id.id] += line
for sols in sols_by_so_dict.values():
if len(sols) > 1 and all(sols.mapped('is_service')):
result += [(
line.id,
'%s - %s' % (
names.get(line.id), format_amount(self.env, line.price_unit, line.currency_id))
) for line in sols]
else:
result += [(line.id, names.get(line.id)) for line in sols]
return result
return res
@api.depends('product_id.type')
def _compute_is_service(self):
for so_line in self:
so_line.is_service = so_line.product_id.type == 'service'
@api.depends('product_id.type')
def _compute_product_updatable(self):
super()._compute_product_updatable()
@@ -56,21 +28,6 @@ class SaleOrderLine(models.Model):
if line.product_id.type == 'service' and line.state == 'sale':
line.product_updatable = False
def _auto_init(self):
"""
Create column to stop ORM from computing it himself (too slow)
"""
if not column_exists(self.env.cr, 'sale_order_line', 'is_service'):
create_column(self.env.cr, 'sale_order_line', 'is_service', 'bool')
self.env.cr.execute("""
UPDATE sale_order_line line
SET is_service = (pt.type = 'service')
FROM product_product pp
LEFT JOIN product_template pt ON pt.id = pp.product_tmpl_id
WHERE pp.id = line.product_id
""")
return super()._auto_init()
@api.depends('product_id')
def _compute_qty_delivered_method(self):
milestones_lines = self.filtered(lambda sol:
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
+14
View File
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': "Sales - Service",
'summary': "Interaction between Sales and services apps (project and planning)",
'description': """
Allows to display sale information in the SOL services apps
===========================================================
Additional information is displayed in the name of the SOL when it is used in services apps (project and planning).
""",
'category': 'Hidden',
'depends': ['sale_management'],
'license': 'LGPL-3',
}
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import sale_order_line
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from itertools import groupby
from odoo import api, fields, models
from odoo.tools import format_amount
from odoo.tools.sql import column_exists, create_column
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
# used to know if generate a task and/or a project, depending on the product settings
is_service = fields.Boolean("Is a Service", compute='_compute_is_service', store=True, compute_sudo=True)
@api.depends('product_id.type')
def _compute_is_service(self):
for so_line in self:
so_line.is_service = so_line.product_id.type == 'service'
def _auto_init(self):
"""
Create column to stop ORM from computing it himself (too slow)
"""
if not column_exists(self.env.cr, 'sale_order_line', 'is_service'):
create_column(self.env.cr, 'sale_order_line', 'is_service', 'bool')
self.env.cr.execute("""
UPDATE sale_order_line line
SET is_service = (pt.type = 'service')
FROM product_product pp
LEFT JOIN product_template pt ON pt.id = pp.product_tmpl_id
WHERE pp.id = line.product_id
""")
return super()._auto_init()
def _additional_name_per_id(self):
name_per_id = super()._additional_name_per_id() if not self.env.context.get('hide_partner_ref') else {}
if not self.env.context.get('with_price_unit'):
return name_per_id
sols_list = [list(sols) for dummy, sols in groupby(self, lambda sol: (sol.order_id, sol.product_id))]
for sols in sols_list:
if len(sols) <= 1 or not all(sol.is_service for sol in sols):
continue
for line in sols:
additional_name = name_per_id.get(line.id)
name = format_amount(self.env, line.price_unit, line.currency_id)
if additional_name:
name += f' {additional_name}'
name_per_id[line.id] = f'- {name}'
return name_per_id