Skip to content

Commit 7262b96

Browse files
chienandaluEmilioPascual
authored andcommitted
[ADD] sale_order_line_price_lock_by_pricelist: New module
MT-9860
1 parent 8598203 commit 7262b96

File tree

18 files changed

+518
-0
lines changed

18 files changed

+518
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2025 Moduon Team S.L.
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
3+
{
4+
"name": "Sale line locking by pricelist",
5+
"summary": "Lock price or discount edition depending on pricelist items",
6+
"version": "16.0.1.0.0",
7+
"development_status": "Alpha",
8+
"category": "Sales Management",
9+
"website": "https://github.com/OCA/sale-workflow",
10+
"author": "Moduon, Odoo Community Association (OCA)",
11+
"maintainers": ["chienandalu", "rafaelbn"],
12+
"license": "LGPL-3",
13+
"depends": [
14+
"sale",
15+
"base_view_inheritance_extension",
16+
],
17+
"data": [
18+
"views/product_pricelist_views.xml",
19+
"views/sale_order_views.xml",
20+
],
21+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Translation of Odoo Server.
2+
# This file contains the translation of the following modules:
3+
# * sale_order_line_price_lock_by_pricelist
4+
#
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: Odoo Server 16.0+e\n"
8+
"Report-Msgid-Bugs-To: \n"
9+
"POT-Creation-Date: 2025-05-14 07:34+0000\n"
10+
"PO-Revision-Date: 2025-05-14 09:40+0200\n"
11+
"Last-Translator: \n"
12+
"Language-Team: \n"
13+
"Language: es\n"
14+
"MIME-Version: 1.0\n"
15+
"Content-Type: text/plain; charset=UTF-8\n"
16+
"Content-Transfer-Encoding: 8bit\n"
17+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
18+
"X-Generator: Poedit 3.4.4\n"
19+
20+
#. module: sale_order_line_price_lock_by_pricelist
21+
#: model:ir.model.fields.selection,name:sale_order_line_price_lock_by_pricelist.selection__product_pricelist__lock_product_prices_applied_on__3_global
22+
msgid "All products"
23+
msgstr "Todos los productos"
24+
25+
#. module: sale_order_line_price_lock_by_pricelist
26+
#: model:ir.model.fields,help:sale_order_line_price_lock_by_pricelist.field_product_pricelist__lock_product_prices_applied_on
27+
msgid ""
28+
"Avoid that salesmen change prices based on this scope. Rules applied on "
29+
"base pricelist take into account the items of those pricelists with the "
30+
"rule of the pricelist applied on the sale order"
31+
msgstr ""
32+
"Evitar que el comercial cambie precios basados en este ámbito. Se aplica el "
33+
"ámbito de la tarifa aplicada aunque se herede la regla de otra tarifa"
34+
35+
#. module: sale_order_line_price_lock_by_pricelist
36+
#: model:ir.model.fields,field_description:sale_order_line_price_lock_by_pricelist.field_product_pricelist__lock_product_prices_applied_on
37+
msgid "Lock price when rule is applied on"
38+
msgstr "Bloquear los precios cuando la tarifa se aplica sobre"
39+
40+
#. module: sale_order_line_price_lock_by_pricelist
41+
#: model_terms:ir.ui.view,arch_db:sale_order_line_price_lock_by_pricelist.product_pricelist_view
42+
msgid "Lock prices"
43+
msgstr "Bloquear precios"
44+
45+
#. module: sale_order_line_price_lock_by_pricelist
46+
#: model:ir.model.fields,field_description:sale_order_line_price_lock_by_pricelist.field_sale_order_line__origin_pricelist_item_id
47+
msgid "Origin Pricelist Item"
48+
msgstr "Elemento de tarifa de origen"
49+
50+
#. module: sale_order_line_price_lock_by_pricelist
51+
#: model:ir.model.fields,field_description:sale_order_line_price_lock_by_pricelist.field_sale_order_line__price_locked
52+
msgid "Price Locked"
53+
msgstr "Precio bloqueado"
54+
55+
#. module: sale_order_line_price_lock_by_pricelist
56+
#: model:ir.model,name:sale_order_line_price_lock_by_pricelist.model_product_pricelist
57+
msgid "Pricelist"
58+
msgstr "Lista de precios"
59+
60+
#. module: sale_order_line_price_lock_by_pricelist
61+
#: model:ir.model.fields.selection,name:sale_order_line_price_lock_by_pricelist.selection__product_pricelist__lock_product_prices_applied_on__1_product
62+
msgid "Product"
63+
msgstr "Producto"
64+
65+
#. module: sale_order_line_price_lock_by_pricelist
66+
#: model:ir.model.fields.selection,name:sale_order_line_price_lock_by_pricelist.selection__product_pricelist__lock_product_prices_applied_on__2_product_category
67+
msgid "Product Category"
68+
msgstr "Categoría de producto"
69+
70+
#. module: sale_order_line_price_lock_by_pricelist
71+
#: model:ir.model.fields.selection,name:sale_order_line_price_lock_by_pricelist.selection__product_pricelist__lock_product_prices_applied_on__0_product_variant
72+
msgid "Product Variant"
73+
msgstr "Variante de producto"
74+
75+
#. module: sale_order_line_price_lock_by_pricelist
76+
#: model:ir.model,name:sale_order_line_price_lock_by_pricelist.model_sale_order_line
77+
msgid "Sales Order Line"
78+
msgstr "Línea de pedido de venta"
79+
80+
#. module: sale_order_line_price_lock_by_pricelist
81+
#: model:ir.model.fields,help:sale_order_line_price_lock_by_pricelist.field_sale_order_line__origin_pricelist_item_id
82+
msgid ""
83+
"Technical field to get the base pricelist item that has the origin price "
84+
"calculation"
85+
msgstr ""
86+
"Campo técnico para recoger la regla de tarifa de base sobre la que se "
87+
"aplica el cálculo del precio"
88+
89+
#. module: sale_order_line_price_lock_by_pricelist
90+
#: model:ir.model.fields,help:sale_order_line_price_lock_by_pricelist.field_sale_order_line__price_locked
91+
msgid ""
92+
"This flag will make the price_unit and discount readonly based on the "
93+
"pricelist locking criterias"
94+
msgstr ""
95+
"Esta marca hará que el precio unitario y el descuento sean de solo lectura "
96+
"basado en los criterios de bloqueo de precios de la tarifa"
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Translation of Odoo Server.
2+
# This file contains the translation of the following modules:
3+
# * sale_order_line_price_lock_by_pricelist
4+
#
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: Odoo Server 16.0+e\n"
8+
"Report-Msgid-Bugs-To: \n"
9+
"POT-Creation-Date: 2025-05-14 07:33+0000\n"
10+
"PO-Revision-Date: 2025-05-14 07:33+0000\n"
11+
"Last-Translator: \n"
12+
"Language-Team: \n"
13+
"MIME-Version: 1.0\n"
14+
"Content-Type: text/plain; charset=UTF-8\n"
15+
"Content-Transfer-Encoding: \n"
16+
"Plural-Forms: \n"
17+
18+
#. module: sale_order_line_price_lock_by_pricelist
19+
#: model:ir.model.fields.selection,name:sale_order_line_price_lock_by_pricelist.selection__product_pricelist__lock_product_prices_applied_on__3_global
20+
msgid "All products"
21+
msgstr ""
22+
23+
#. module: sale_order_line_price_lock_by_pricelist
24+
#: model:ir.model.fields,help:sale_order_line_price_lock_by_pricelist.field_product_pricelist__lock_product_prices_applied_on
25+
msgid ""
26+
"Avoid that salesmen change prices based on this scope. Rules applied on base"
27+
" pricelist take into account the items of those pricelists with the rule of "
28+
"the pricelist applied on the sale order"
29+
msgstr ""
30+
31+
#. module: sale_order_line_price_lock_by_pricelist
32+
#: model:ir.model.fields,field_description:sale_order_line_price_lock_by_pricelist.field_product_pricelist__lock_product_prices_applied_on
33+
msgid "Lock price when rule is applied on"
34+
msgstr ""
35+
36+
#. module: sale_order_line_price_lock_by_pricelist
37+
#: model_terms:ir.ui.view,arch_db:sale_order_line_price_lock_by_pricelist.product_pricelist_view
38+
msgid "Lock prices"
39+
msgstr ""
40+
41+
#. module: sale_order_line_price_lock_by_pricelist
42+
#: model:ir.model.fields,field_description:sale_order_line_price_lock_by_pricelist.field_sale_order_line__origin_pricelist_item_id
43+
msgid "Origin Pricelist Item"
44+
msgstr ""
45+
46+
#. module: sale_order_line_price_lock_by_pricelist
47+
#: model:ir.model.fields,field_description:sale_order_line_price_lock_by_pricelist.field_sale_order_line__price_locked
48+
msgid "Price Locked"
49+
msgstr ""
50+
51+
#. module: sale_order_line_price_lock_by_pricelist
52+
#: model:ir.model,name:sale_order_line_price_lock_by_pricelist.model_product_pricelist
53+
msgid "Pricelist"
54+
msgstr ""
55+
56+
#. module: sale_order_line_price_lock_by_pricelist
57+
#: model:ir.model.fields.selection,name:sale_order_line_price_lock_by_pricelist.selection__product_pricelist__lock_product_prices_applied_on__1_product
58+
msgid "Product"
59+
msgstr ""
60+
61+
#. module: sale_order_line_price_lock_by_pricelist
62+
#: model:ir.model.fields.selection,name:sale_order_line_price_lock_by_pricelist.selection__product_pricelist__lock_product_prices_applied_on__2_product_category
63+
msgid "Product Category"
64+
msgstr ""
65+
66+
#. module: sale_order_line_price_lock_by_pricelist
67+
#: model:ir.model.fields.selection,name:sale_order_line_price_lock_by_pricelist.selection__product_pricelist__lock_product_prices_applied_on__0_product_variant
68+
msgid "Product Variant"
69+
msgstr ""
70+
71+
#. module: sale_order_line_price_lock_by_pricelist
72+
#: model:ir.model,name:sale_order_line_price_lock_by_pricelist.model_sale_order_line
73+
msgid "Sales Order Line"
74+
msgstr ""
75+
76+
#. module: sale_order_line_price_lock_by_pricelist
77+
#: model:ir.model.fields,help:sale_order_line_price_lock_by_pricelist.field_sale_order_line__origin_pricelist_item_id
78+
msgid ""
79+
"Technical field to get the base pricelist item that has the origin price "
80+
"calculation"
81+
msgstr ""
82+
83+
#. module: sale_order_line_price_lock_by_pricelist
84+
#: model:ir.model.fields,help:sale_order_line_price_lock_by_pricelist.field_sale_order_line__price_locked
85+
msgid ""
86+
"This flag will make the price_unit and discount readonly based on the "
87+
"pricelist locking criterias"
88+
msgstr ""
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import product_pricelist
2+
from . import sale_order
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright 2025 Moduon Team S.L.
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
3+
from odoo import fields, models
4+
5+
6+
class ProductPricelist(models.Model):
7+
_inherit = "product.pricelist"
8+
9+
lock_product_prices_applied_on = fields.Selection(
10+
string="Lock price when rule is applied on",
11+
selection=[
12+
("3_global", "All products"),
13+
("2_product_category", "Product Category"),
14+
("1_product", "Product"),
15+
("0_product_variant", "Product Variant"),
16+
],
17+
help="Avoid that salesmen change prices based on this scope. Rules applied on "
18+
"base pricelist take into account the items of those pricelists with the rule "
19+
"of the pricelist applied on the sale order",
20+
)
21+
22+
def _get_base_product_rule(self, product, quantity, uom=None, date=False, **kwargs):
23+
"""Recurrent method to pull the pricelist item from the pricelist chain of
24+
inheritance"""
25+
self.ensure_one()
26+
item = self.env["product.pricelist.item"].browse(
27+
self._get_product_rule(product, quantity, uom, date, **kwargs)
28+
)
29+
if item.base and item.base == "pricelist" and item.base_pricelist_id:
30+
return item.base_pricelist_id._get_base_product_rule(
31+
product, quantity, uom, date, **kwargs
32+
)
33+
return item.id
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2025 Moduon Team S.L.
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
3+
from odoo import api, fields, models
4+
5+
6+
class SaleOrder(models.Model):
7+
_inherit = "sale.order.line"
8+
9+
origin_pricelist_item_id = fields.Many2one(
10+
comodel_name="product.pricelist.item",
11+
compute="_compute_origin_pricelist_item_id",
12+
help="Technical field to get the base pricelist item that has the origin "
13+
"price calculation",
14+
)
15+
price_locked = fields.Boolean(
16+
compute="_compute_price_locked",
17+
help="This flag will make the price_unit and discount readonly based on the "
18+
"pricelist locking criterias",
19+
)
20+
21+
@api.depends("pricelist_item_id")
22+
def _compute_origin_pricelist_item_id(self):
23+
for line in self:
24+
# Just compute those items which price is pulled from another pricelist
25+
if (
26+
line.pricelist_item_id.base
27+
and line.pricelist_item_id.base == "pricelist"
28+
and line.pricelist_item_id.base_pricelist_id
29+
):
30+
line.origin_pricelist_item_id = (
31+
line.order_id.pricelist_id._get_base_product_rule(
32+
line.product_id,
33+
line.product_uom_qty or 1.0,
34+
uom=line.product_uom,
35+
date=line._get_order_date(),
36+
)
37+
)
38+
else:
39+
line.origin_pricelist_item_id = line.pricelist_item_id
40+
41+
@api.depends("origin_pricelist_item_id", "pricelist_item_id")
42+
def _compute_price_locked(self):
43+
self.price_locked = False
44+
# Sales manager are able to change pricelists so they shouldn't be block
45+
if self.env.user.has_group("sales_team.group_sale_manager"):
46+
return
47+
self.filtered(
48+
lambda x: (
49+
x.origin_pricelist_item_id
50+
and x.pricelist_item_id.pricelist_id.lock_product_prices_applied_on
51+
and (
52+
x.origin_pricelist_item_id.applied_on
53+
<= x.pricelist_item_id.pricelist_id.lock_product_prices_applied_on
54+
)
55+
)
56+
or (
57+
not x.origin_pricelist_item_id
58+
and x.pricelist_item_id.pricelist_id.lock_product_prices_applied_on
59+
== "3_global"
60+
)
61+
).price_locked = True
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
In order to configure the prices lock scope, go to the pricelist you want to configure
2+
or create a new one from *Sales > Products > Pricelists*.
3+
4+
Now, in the *Configuration* tab, you can set the scope on the field
5+
**Lock product prices applied on*. The possible options are:
6+
7+
- All products: any rule will lock the price. Very restrictive.
8+
- Product category: all the rules applied on product category, product or variants will
9+
lock the price.
10+
- Product: all the rules applied on product or variants will lock the price.
11+
- Product variant: only the rules applied on variants will lock the price.
12+
13+
For this example, we'll choose the *Product* scope.
14+
15+
Then, in the price rules, we'll add a fixed price of 80 to be applied on the product
16+
*FURN_5555*. Save and close and go to the **Usage** section to test it in the sales order.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Sometimes we want to avoid that salesmen are able to modify prices that are fixed and
2+
negociated with the customer in advance.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Rafael Blasco ([Moduon](https://www.moduon.team/))
2+
- David Vidal ([Moduon](https://www.moduon.team/))

0 commit comments

Comments
 (0)