diff --git a/sale_pricelist_global_rule/README.rst b/sale_pricelist_global_rule/README.rst
index f84d72645c0d..d2db8f5fae0b 100644
--- a/sale_pricelist_global_rule/README.rst
+++ b/sale_pricelist_global_rule/README.rst
@@ -17,13 +17,13 @@ Sale pricelist global rule
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
- :target: https://github.com/OCA/sale-workflow/tree/15.0/sale_pricelist_global_rule
+ :target: https://github.com/OCA/sale-workflow/tree/16.0/sale_pricelist_global_rule
:alt: OCA/sale-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/sale-workflow-15-0/sale-workflow-15-0-sale_pricelist_global_rule
+ :target: https://translation.odoo-community.org/projects/sale-workflow-16-0/sale-workflow-16-0-sale_pricelist_global_rule
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
- :target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=15.0
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -80,7 +80,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -113,6 +113,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-This module is part of the `OCA/sale-workflow `_ project on GitHub.
+This module is part of the `OCA/sale-workflow `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/sale_pricelist_global_rule/__manifest__.py b/sale_pricelist_global_rule/__manifest__.py
index 4335f479d53b..956abf01f546 100644
--- a/sale_pricelist_global_rule/__manifest__.py
+++ b/sale_pricelist_global_rule/__manifest__.py
@@ -1,6 +1,6 @@
{
"name": "Sale pricelist global rule",
- "version": "15.0.1.0.3",
+ "version": "16.0.1.0.0",
"summary": "Apply a global rule to all sale order",
"author": "Tecnativa, Odoo Community Association (OCA)",
"category": "Sales Management",
diff --git a/sale_pricelist_global_rule/models/product_pricelist.py b/sale_pricelist_global_rule/models/product_pricelist.py
index 3654bc9eb429..47cb39b58530 100644
--- a/sale_pricelist_global_rule/models/product_pricelist.py
+++ b/sale_pricelist_global_rule/models/product_pricelist.py
@@ -2,16 +2,50 @@
from odoo.exceptions import ValidationError
+def get_family_category(category):
+ subcategories = set(category.child_id)
+ all_subcategories = subcategories.copy()
+ for subcategory in subcategories:
+ all_subcategories |= get_family_category(subcategory)
+ return all_subcategories
+
+
class ProductPricelist(models.Model):
_inherit = "product.pricelist"
def _compute_price_rule_get_items(
self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
):
- items = super()._compute_price_rule_get_items(
- products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
+ self.ensure_one()
+ # Load all rules
+ self.env["product.pricelist.item"].flush(
+ ["price", "currency_id", "company_id", "active"]
)
- # ignore new global rules on Odoo standard
+ self.env.cr.execute(
+ """
+ SELECT
+ item.id
+ FROM
+ product_pricelist_item AS item
+ LEFT JOIN product_category AS categ ON item.categ_id = categ.id
+ WHERE
+ (item.product_tmpl_id IS NULL OR item.product_tmpl_id = any(%s))
+ AND (item.product_id IS NULL OR item.product_id = any(%s))
+ AND (item.categ_id IS NULL OR item.categ_id = any(%s))
+ AND (item.pricelist_id = %s)
+ AND (item.date_start IS NULL OR item.date_start<=%s)
+ AND (item.date_end IS NULL OR item.date_end>=%s)
+ AND (item.active = TRUE)
+ ORDER BY
+ item.applied_on, item.min_quantity desc, categ.complete_name desc, item.id desc
+ """,
+ (prod_tmpl_ids, prod_ids, categ_ids, self.id, date, date),
+ )
+ # NOTE: if you change `order by` on that query, make sure it matches
+ # _order from model to avoid inconstencies and undeterministic issues.
+
+ item_ids = [x[0] for x in self.env.cr.fetchall()]
+ items = self.env["product.pricelist.item"].browse(item_ids)
return items.filtered(
lambda item: item.applied_on
not in ["4_global_product_template", "5_global_product_category"]
@@ -82,6 +116,7 @@ def _compute_price_rule_global(self, sale):
"by_template": {},
"by_categ": {},
}
+
for line in sale.order_line.filtered(lambda x: not x.display_type):
qty_in_product_uom = line.product_uom_qty
# Final unit price is computed according to `qty` in the default `uom_id`.
@@ -111,10 +146,10 @@ def _compute_price_rule_global(self, sale):
# need to call price_compute.
price = product.price_compute("list_price")[product.id]
- price_uom = product.uom_id
for rule in items:
if not rule._is_applicable_for_sale(product.product_tmpl_id, qty_data):
continue
+
if rule.base == "pricelist" and rule.base_pricelist_id:
# first, try compute the price for global rule
# otherwise, fallback to regular computation
@@ -125,9 +160,7 @@ def _compute_price_rule_global(self, sale):
) = rule.base_pricelist_id._compute_price_rule_global(sale)[line.id]
if not rule_applied:
price = rule.base_pricelist_id._compute_price_rule(
- [(product, line.product_uom_qty, sale.partner_id)],
- date,
- line.product_uom.id,
+ product, line.product_uom_qty
)[product.id][0]
src_currency = rule.base_pricelist_id.currency_id
else:
@@ -143,9 +176,15 @@ def _compute_price_rule_global(self, sale):
price = src_currency._convert(
price, self.currency_id, self.env.company, date, round=False
)
-
+ # import pdb; pdb.set_trace()
if price is not False:
- price = rule._compute_price(price, price_uom, product)
+ price = rule._compute_price(
+ product,
+ line.product_uom_qty,
+ product.uom_id,
+ date,
+ self.currency_id,
+ )
suitable_rule = rule
break
@@ -231,8 +270,8 @@ def _check_product_consistency(self):
"price_discount",
"price_surcharge",
)
- def _get_pricelist_item_name_price(self):
- res = super()._get_pricelist_item_name_price()
+ def _compute_name_and_price(self):
+ res = super()._compute_name_and_price()
for item in self:
if item.global_categ_id and item.applied_on == "5_global_product_category":
item.name = _("Global category: %s") % (
@@ -318,11 +357,17 @@ def _is_applicable_for_sale(self, product_template, qty_data):
elif self.global_product_tmpl_id != product_template:
is_applicable = False
elif self.applied_on == "5_global_product_category":
- total_qty = qty_data["by_categ"].get(product_template.categ_id, 0.0)
+ category = product_template.categ_id
+ total_qty = qty_data["by_categ"].get(category, 0.0)
if self.min_quantity and total_qty < self.min_quantity:
is_applicable = False
- elif not product_template.categ_id.parent_path.startswith(
- self.global_categ_id.parent_path
- ):
- is_applicable = False
+ else:
+ rule_family_categories = get_family_category(self.global_categ_id)
+ rule_family_categories.add(self.global_categ_id)
+
+ product_family_categories = get_family_category(category)
+ product_family_categories.add(category)
+
+ if not rule_family_categories.intersection(product_family_categories):
+ is_applicable = False
return is_applicable
diff --git a/sale_pricelist_global_rule/models/sale_order.py b/sale_pricelist_global_rule/models/sale_order.py
index d41605a9dcd2..2da79740f4b5 100644
--- a/sale_pricelist_global_rule/models/sale_order.py
+++ b/sale_pricelist_global_rule/models/sale_order.py
@@ -1,3 +1,5 @@
+import math
+
from odoo import api, fields, models
from odoo.tools import float_compare
@@ -47,6 +49,7 @@ def button_compute_pricelist_global_rule(self):
fiscal_position=self.env.context.get("fiscal_position"),
)
price, suitable_rule = prices_data[line.id]
+ price = math.trunc(price * 100) / 100
if is_discount_visible:
product_context = dict(
self.env.context,
@@ -55,15 +58,10 @@ def button_compute_pricelist_global_rule(self):
uom=line.product_uom.id,
)
- base_price, currency = line.with_context(
+ currency = self.pricelist_id.currency_id
+ base_price = line.with_context(
**product_context
- )._get_real_price_currency(
- product,
- suitable_rule,
- line.product_uom_qty,
- line.product_uom,
- self.pricelist_id.id,
- )
+ ).product_id.uom_id._compute_price(product.lst_price, line.product_uom)
if base_price != 0:
if self.pricelist_id.currency_id != currency:
# we need new_list_price in the same currency as price,
@@ -75,6 +73,7 @@ def button_compute_pricelist_global_rule(self):
self.date_order or fields.Date.context_today(self),
)
discount = (base_price - price) / base_price * 100
+
if (discount > 0 and base_price > 0) or (
discount < 0 and base_price < 0
):
@@ -84,5 +83,6 @@ def button_compute_pricelist_global_rule(self):
if float_compare(price, line.price_unit, precision_digits=digits) != 0:
vals_to_write["price_unit"] = price
if vals_to_write:
+
line.write(vals_to_write)
self.need_recompute_pricelist_global = False
diff --git a/sale_pricelist_global_rule/static/description/index.html b/sale_pricelist_global_rule/static/description/index.html
index 6be684c49327..19a46179c63f 100644
--- a/sale_pricelist_global_rule/static/description/index.html
+++ b/sale_pricelist_global_rule/static/description/index.html
@@ -369,7 +369,7 @@ Sale pricelist global rule
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:b93829a100ce8864f9f399ba2bf2c998544d9a7b00d3555405045eaebe3f4cd4
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-
+
This module allows configured pricelists to be applied to a sales order by considering cumulative quantities across all lines.
Global by Product Template
If a pricelist rule has a min_quantity = 15, and a sales order contains:
@@ -430,7 +430,7 @@
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
-feedback.
+feedback.
Do not contact contributors directly about support or help with technical issues.
@@ -460,7 +460,7 @@
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-
This module is part of the OCA/sale-workflow project on GitHub.
+
This module is part of the OCA/sale-workflow project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/sale_pricelist_global_rule/tests/test_pricelist_global.py b/sale_pricelist_global_rule/tests/test_pricelist_global.py
index 31a5c1adad52..dca251e170ab 100644
--- a/sale_pricelist_global_rule/tests/test_pricelist_global.py
+++ b/sale_pricelist_global_rule/tests/test_pricelist_global.py
@@ -683,14 +683,14 @@ def test_pricelist_visible_discount(self):
- Based on other pricelist
- Global pricelist discount policy: without_discount
- base pricelist discount_policy: with_discount
- - product_m_red: price=80, discount=10
- - product_m_black: price=80, discount=10
+ - product_m_red: price=100, discount=28
+ - product_m_black: price=100, discount=28
Case 5:
- Based on other pricelist
- Global pricelist discount policy: with_discount
- base pricelist discount_policy: without_discount
- - product_m_red: price=80, discount=10
- - product_m_black: price=80, discount=10
+ - product_m_red: price=72, discount=0
+ - product_m_black: price=72, discount=0
Case 6:
- Based on other pricelist
- Global pricelist discount policy: without_discount
@@ -744,10 +744,10 @@ def test_pricelist_visible_discount(self):
self.pricelist_global.write({"discount_policy": "without_discount"})
self.pricelist_base.write({"discount_policy": "with_discount"})
self.sale_order1.button_compute_pricelist_global_rule()
- self.assertEqual(self.sale_line_m_red.price_unit, 80)
- self.assertEqual(self.sale_line_m_red.discount, 10)
- self.assertEqual(self.sale_line_m_black.price_unit, 80)
- self.assertEqual(self.sale_line_m_black.discount, 10)
+ self.assertEqual(self.sale_line_m_red.price_unit, 100)
+ self.assertEqual(self.sale_line_m_red.discount, 28)
+ self.assertEqual(self.sale_line_m_black.price_unit, 100)
+ self.assertEqual(self.sale_line_m_black.discount, 28)
self.assertEqual(self.sale_line_2.price_unit, 200)
self.assertEqual(self.sale_line_2.discount, 0)
self.assertEqual(self.sale_line_3.price_unit, 300)
diff --git a/setup/sale_pricelist_global_rule/odoo/addons/sale_pricelist_global_rule b/setup/sale_pricelist_global_rule/odoo/addons/sale_pricelist_global_rule
new file mode 120000
index 000000000000..6dbba224015f
--- /dev/null
+++ b/setup/sale_pricelist_global_rule/odoo/addons/sale_pricelist_global_rule
@@ -0,0 +1 @@
+../../../../sale_pricelist_global_rule
\ No newline at end of file
diff --git a/setup/sale_pricelist_global_rule/setup.py b/setup/sale_pricelist_global_rule/setup.py
new file mode 100644
index 000000000000..28c57bb64031
--- /dev/null
+++ b/setup/sale_pricelist_global_rule/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+
+setuptools.setup(
+ setup_requires=['setuptools-odoo'],
+ odoo_addon=True,
+)