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.
diff --git a/sale_order_general_discount/models/sale_order_line.py b/sale_order_general_discount/models/sale_order_line.py
index a1f5cd15155..cdb3b096293 100644
--- a/sale_order_general_discount/models/sale_order_line.py
+++ b/sale_order_general_discount/models/sale_order_line.py
@@ -15,5 +15,9 @@ def _compute_discount(self):
# set again to 0 to remove the discount on all the lines at the same
# time
if line.order_id.general_discount or line.order_id._origin.general_discount:
- line.discount = line.order_id.general_discount
+ if "discount1" in self._fields:
+ # Compatibility with sale_triple_discount module
+ line.discount1 = line.order_id.general_discount
+ else:
+ line.discount = line.order_id.general_discount
return res
diff --git a/sale_order_general_discount_triple/models/res_config_settings.py b/sale_order_general_discount_triple/models/res_config_settings.py
index ec91613c5fc..fddbdf70903 100644
--- a/sale_order_general_discount_triple/models/res_config_settings.py
+++ b/sale_order_general_discount_triple/models/res_config_settings.py
@@ -6,7 +6,7 @@ class ResConfigSettings(models.TransientModel):
general_discount = fields.Selection(
[
- ("discount", "Discount"),
+ ("discount1", "Discount 1"),
("discount2", "Discount 2"),
("discount3", "Discount 3"),
],
@@ -15,7 +15,7 @@ class ResConfigSettings(models.TransientModel):
)
pricelist_discount = fields.Selection(
[
- ("discount", "Discount"),
+ ("discount1", "Discount 1"),
("discount2", "Discount 2"),
("discount3", "Discount 3"),
],
diff --git a/sale_order_general_discount_triple/models/sale_order.py b/sale_order_general_discount_triple/models/sale_order.py
index c9285f09dd5..fbc45478bc5 100644
--- a/sale_order_general_discount_triple/models/sale_order.py
+++ b/sale_order_general_discount_triple/models/sale_order.py
@@ -10,7 +10,7 @@ def onchange_general_discount(self):
self.env["ir.config_parameter"]
.sudo()
.get_param(
- "sale_order_general_discount_triple.general_discount", "discount"
+ "sale_order_general_discount_triple.general_discount", "discount1"
)
)
if general_discount != "no_apply":
@@ -20,7 +20,7 @@ def onchange_general_discount(self):
def _create_delivery_line(self, carrier, price_unit):
res = super()._create_delivery_line(carrier, price_unit)
for line in self.order_line:
- line._compute_discount()
+ line._compute_discount1()
line._compute_discount2()
line._compute_discount3()
return res
diff --git a/sale_order_general_discount_triple/models/sale_order_line.py b/sale_order_general_discount_triple/models/sale_order_line.py
index 90839823ded..fcada4ffdaf 100644
--- a/sale_order_general_discount_triple/models/sale_order_line.py
+++ b/sale_order_general_discount_triple/models/sale_order_line.py
@@ -4,21 +4,22 @@
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
+ discount1 = fields.Float(compute="_compute_discount1", store=True, readonly=False)
discount2 = fields.Float(compute="_compute_discount2", store=True, readonly=False)
discount3 = fields.Float(compute="_compute_discount3", store=True, readonly=False)
@api.depends("product_id", "product_uom", "product_uom_qty")
- def _compute_discount(self):
+ def _compute_discount1(self):
pricelist_discount = self._get_discount_field_position("pricelist_discount")
general_discount = self._get_discount_field_position("general_discount")
- if "discount" not in [pricelist_discount, general_discount]:
- self.update({"discount": 0.0})
+ if "discount1" not in [pricelist_discount, general_discount]:
+ self.update({"discount1": 0.0})
return
for line in self:
- if pricelist_discount == "discount":
- line.update({"discount": line._get_pricelist_discount()})
- elif general_discount == "discount":
- line.update({"discount": line.order_id.general_discount})
+ if pricelist_discount == "discount1":
+ line.update({"discount1": line._get_pricelist_discount()})
+ elif general_discount == "discount1":
+ line.update({"discount1": line.order_id.general_discount})
return
@api.depends("product_id", "product_uom", "product_uom_qty")
@@ -71,6 +72,6 @@ def _get_discount_field_position(self, field_name):
self.env["ir.config_parameter"]
.sudo()
.get_param(
- "sale_order_general_discount_triple.{}".format(field_name), "discount"
+ "sale_order_general_discount_triple.{}".format(field_name), "discount1"
)
)
diff --git a/sale_order_general_discount_triple/tests/test_module.py b/sale_order_general_discount_triple/tests/test_module.py
index 3f7f731b4be..706ac9d9297 100644
--- a/sale_order_general_discount_triple/tests/test_module.py
+++ b/sale_order_general_discount_triple/tests/test_module.py
@@ -29,7 +29,7 @@ def setUpClass(cls):
)
setting_form = Form(cls.env["res.config.settings"])
setting_form.general_discount = "discount2"
- setting_form.pricelist_discount = "discount"
+ setting_form.pricelist_discount = "discount1"
setting_form.group_discount_per_so_line = True
setting_form.save().set_values()
@@ -41,4 +41,4 @@ def test_action_result(self):
line.product_id = self.product
sale = sale_form.save()
self.assertEqual(sale.order_line.discount2, 10)
- self.assertEqual(sale.order_line.discount, 20)
+ self.assertEqual(sale.order_line.discount1, 20)
diff --git a/sale_triple_discount/README.rst b/sale_triple_discount/README.rst
index 2f2c5340362..a2f5ac77854 100644
--- a/sale_triple_discount/README.rst
+++ b/sale_triple_discount/README.rst
@@ -109,6 +109,7 @@ Contributors
* Pimolnat Suntian
* Denis Leemann
* Manuel Regidor
+* Akim Juillerat
Maintainers
~~~~~~~~~~~
diff --git a/sale_triple_discount/__init__.py b/sale_triple_discount/__init__.py
index 0650744f6bc..f1a5233b277 100644
--- a/sale_triple_discount/__init__.py
+++ b/sale_triple_discount/__init__.py
@@ -1 +1,2 @@
from . import models
+from .hooks import post_load
diff --git a/sale_triple_discount/__manifest__.py b/sale_triple_discount/__manifest__.py
index 47833af14a1..274dae70748 100644
--- a/sale_triple_discount/__manifest__.py
+++ b/sale_triple_discount/__manifest__.py
@@ -6,7 +6,7 @@
{
"name": "Sale Triple Discount",
- "version": "16.0.1.0.4",
+ "version": "16.0.2.0.0",
"category": "Sales",
"author": "ADHOC SA, Agile Business Group, Tecnativa, "
"Odoo Community Association (OCA)",
@@ -14,6 +14,8 @@
"license": "AGPL-3",
"summary": "Manage triple discount on sale order lines",
"depends": ["sale_management", "account_invoice_triple_discount"],
+ "excludes": ["sale_fixed_discount"],
"data": ["views/sale_order_report.xml", "views/sale_order_view.xml"],
"installable": True,
+ "post_load": "post_load",
}
diff --git a/sale_triple_discount/hooks.py b/sale_triple_discount/hooks.py
new file mode 100644
index 00000000000..7813d820ed2
--- /dev/null
+++ b/sale_triple_discount/hooks.py
@@ -0,0 +1,24 @@
+# Copyright 2024 Camptocamp SA
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
+import logging
+
+import pkg_resources
+
+from odoo.modules.module import get_manifest
+
+_logger = logging.getLogger(__name__)
+
+
+def post_load():
+ account_invoice_triple_discount_manifest = get_manifest(
+ "account_invoice_triple_discount"
+ )
+ if not pkg_resources.parse_version(
+ account_invoice_triple_discount_manifest["version"]
+ ) >= pkg_resources.parse_version("16.0.2.0.0"):
+ msg = (
+ "Module sale_triple_discount requires module "
+ "account_invoice_triple_discount >= 16.0.2.0.0"
+ )
+ _logger.error(msg)
+ raise Exception(msg)
diff --git a/sale_triple_discount/migrations/16.0.2.0.0/post-migrate.py b/sale_triple_discount/migrations/16.0.2.0.0/post-migrate.py
new file mode 100644
index 00000000000..9bf77242f30
--- /dev/null
+++ b/sale_triple_discount/migrations/16.0.2.0.0/post-migrate.py
@@ -0,0 +1,23 @@
+# Copyright 2024 Camptocamp SA
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
+
+from openupgradelib import openupgrade
+
+
+@openupgrade.logging()
+def compute_discount(env):
+ lines_with_discount = env["sale.order.line"].search(
+ [
+ "|",
+ "|",
+ ("discount1", "!=", 0),
+ ("discount2", "!=", 0),
+ ("discount3", "!=", 0),
+ ]
+ )
+ lines_with_discount._compute_discount_consolidated()
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+ compute_discount(env)
diff --git a/sale_triple_discount/migrations/16.0.2.0.0/pre-migrate.py b/sale_triple_discount/migrations/16.0.2.0.0/pre-migrate.py
new file mode 100644
index 00000000000..a9dd2eec07d
--- /dev/null
+++ b/sale_triple_discount/migrations/16.0.2.0.0/pre-migrate.py
@@ -0,0 +1,32 @@
+# Copyright 2024 Camptocamp SA
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
+from openupgradelib import openupgrade
+
+
+def migrate_discount_to_discount1(env):
+ openupgrade.add_fields(
+ env,
+ [
+ (
+ "discount1",
+ "sale.order.line",
+ "sale_order_line",
+ "float",
+ "numeric",
+ "sale_triple_discount",
+ 0.0,
+ )
+ ],
+ )
+ openupgrade.logged_query(
+ env.cr,
+ """
+ UPDATE sale_order_line
+ SET discount1 = discount;
+ """,
+ )
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+ migrate_discount_to_discount1(env)
diff --git a/sale_triple_discount/models/sale_order_line.py b/sale_triple_discount/models/sale_order_line.py
index 992ecaec478..3b2c7e6135f 100644
--- a/sale_triple_discount/models/sale_order_line.py
+++ b/sale_triple_discount/models/sale_order_line.py
@@ -3,9 +3,6 @@
# Copyright 2017 Tecnativa - David Vidal
# Copyright 2018 Simone Rubino - Agile Business Group
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from contextlib import contextmanager
-
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
@@ -13,6 +10,24 @@
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
+ discount = fields.Float(
+ string="Total discount",
+ store=True,
+ compute="_compute_discount_consolidated",
+ compute_sudo=True,
+ precompute=True,
+ readonly=True,
+ )
+ discount1 = fields.Float(
+ string="Disc. 1 (%)",
+ digits="Discount",
+ store=True,
+ default=0.0,
+ compute="_compute_discount",
+ compute_sudo=True,
+ precompute=True,
+ readonly=False,
+ )
discount2 = fields.Float(
string="Disc. 2 (%)",
digits="Discount",
@@ -60,19 +75,61 @@ def _multiplicative_discount(self):
final_discount = 1
for discount in discounts:
final_discount *= discount
- return 100 - final_discount * 100
+ result = 100 - final_discount * 100
+ dp = self.env.ref("product.decimal_discount").precision_get("Discount")
+ return round(result, dp)
@api.model
def _discount_fields(self):
- return ["discount", "discount2", "discount3"]
+ return ["discount1", "discount2", "discount3"]
- @api.depends("discount2", "discount3", "discounting_type")
- def _compute_amount(self):
- with self._aggregated_discount() as lines:
- res = super(SaleOrderLine, lines)._compute_amount()
- return res
+ # Copy of Odoo function to change field being assigned from discount to discount1
+ @api.depends("product_id", "product_uom", "product_uom_qty")
+ def _compute_discount(self):
+ for line in self:
+ if not line.product_id or line.display_type:
+ line.discount1 = 0.0
+
+ if not (
+ line.order_id.pricelist_id
+ and line.order_id.pricelist_id.discount_policy == "without_discount"
+ ):
+ continue
+
+ line.discount1 = 0.0
+
+ if not line.pricelist_item_id:
+ # No pricelist rule was found for the product
+ # therefore, the pricelist didn't apply any discount/change
+ # to the existing sales price.
+ continue
+
+ line.discount1 = line._calc_discount_from_pricelist()
+
+ def _calc_discount_from_pricelist(self):
+ self.ensure_one()
+ self = self.with_company(self.company_id)
+ pricelist_price = self._get_pricelist_price()
+ base_price = self._get_pricelist_price_before_discount()
+
+ if base_price != 0: # Avoid division by zero
+ discount = (base_price - pricelist_price) / base_price * 100
+ if (discount > 0 and base_price > 0) or (discount < 0 and base_price < 0):
+ # only show negative discounts if price is negative
+ # otherwise it's a surcharge which shouldn't be shown to the customer
+ return discount
+
+ @api.depends("discount1", "discount2", "discount3", "discounting_type")
+ def _compute_discount_consolidated(self):
+ for line in self:
+ line.discount = line._get_final_discount()
_sql_constraints = [
+ (
+ "discount1_limit",
+ "CHECK (discount1 <= 100.0)",
+ "Discount 1 must be lower or equal than 100%.",
+ ),
(
"discount2_limit",
"CHECK (discount2 <= 100.0)",
@@ -91,51 +148,15 @@ def _prepare_invoice_line(self, **kwargs):
more discount fields to the invoice lines
"""
res = super()._prepare_invoice_line(**kwargs)
- res.update({"discount2": self.discount2, "discount3": self.discount3})
+ res.pop("discount", None)
+ if self.discounting_type == "multiplicative":
+ res.update(
+ {
+ "discount1": self.discount1,
+ "discount2": self.discount2,
+ "discount3": self.discount3,
+ }
+ )
+ else:
+ res.update({"discount1": self.discount})
return res
-
- @contextmanager
- def _aggregated_discount(self):
- """A context manager to temporarily change the discount value on the
- records and restore it after the context is exited. It temporarily
- changes the discount value to the aggregated discount value so that
- methods that depend on the discount value will use the aggregated
- discount value instead of the original one.
- """
- discount_field = self._fields["discount"]
- # Protect discount field from triggering recompute of totals. We don't want
- # to invalidate the cache to avoid to flush the records to the database.
- # This is safe because we are going to restore the original value at the end
- # of the method.
- with self.env.protecting([discount_field], self):
- old_values = {}
- for line in self:
- old_values[line.id] = line.discount
- aggregated_discount = line._get_final_discount()
- line.update({"discount": aggregated_discount})
- yield self.with_context(discount_is_aggregated=True)
- for line in self:
- if line.id not in old_values:
- continue
- line.with_context(
- restoring_triple_discount=True,
- ).update({"discount": old_values[line.id]})
-
- def _convert_to_tax_base_line_dict(self):
- self.ensure_one()
- discount = (
- self.discount
- if self.env.context.get("discount_is_aggregated")
- else self._get_final_discount()
- )
- return self.env["account.tax"]._convert_to_tax_base_line_dict(
- self,
- partner=self.order_id.partner_id,
- currency=self.order_id.currency_id,
- product=self.product_id,
- taxes=self.tax_id,
- price_unit=self.price_unit,
- quantity=self.product_uom_qty,
- discount=discount,
- price_subtotal=self.price_subtotal,
- )
diff --git a/sale_triple_discount/readme/CONTRIBUTORS.rst b/sale_triple_discount/readme/CONTRIBUTORS.rst
index b196f4fa786..96c597e91dd 100644
--- a/sale_triple_discount/readme/CONTRIBUTORS.rst
+++ b/sale_triple_discount/readme/CONTRIBUTORS.rst
@@ -7,3 +7,4 @@
* Pimolnat Suntian
* Denis Leemann
* Manuel Regidor
+* Akim Juillerat
diff --git a/sale_triple_discount/static/description/index.html b/sale_triple_discount/static/description/index.html
index 9383f7b48f0..6903072f1a6 100644
--- a/sale_triple_discount/static/description/index.html
+++ b/sale_triple_discount/static/description/index.html
@@ -457,6 +457,7 @@