From f3515c3ae1151101c054d2cd05c60d94a01a7d2b Mon Sep 17 00:00:00 2001 From: sergio-teruel Date: Mon, 22 Jun 2026 15:04:42 +0200 Subject: [PATCH] [FIX] purchase_order_secondary_unit: avoid price recomputation from rounded secondary price --- purchase_order_secondary_unit/README.rst | 14 +++++--- purchase_order_secondary_unit/__manifest__.py | 1 + .../models/product_supplierinfo.py | 13 ++++++++ .../models/purchase_order.py | 13 ++++++++ .../static/description/index.html | 32 ++++++++----------- ...est_product_supplierinfo_secondary_unit.py | 20 ++++++++++++ .../test_purchase_order_secondary_unit.py | 26 +++++++++++++++ 7 files changed, 96 insertions(+), 23 deletions(-) diff --git a/purchase_order_secondary_unit/README.rst b/purchase_order_secondary_unit/README.rst index e321456a744..8f717722905 100644 --- a/purchase_order_secondary_unit/README.rst +++ b/purchase_order_secondary_unit/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ============================= Purchase Order Secondary Unit ============================= @@ -17,7 +13,7 @@ Purchase Order Secondary Unit .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpurchase--workflow-lightgray.png?logo=github @@ -130,6 +126,14 @@ 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. +.. |maintainer-sergio-teruel| image:: https://github.com/sergio-teruel.png?size=40px + :target: https://github.com/sergio-teruel + :alt: sergio-teruel + +Current `maintainer `__: + +|maintainer-sergio-teruel| + This module is part of the `OCA/purchase-workflow `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/purchase_order_secondary_unit/__manifest__.py b/purchase_order_secondary_unit/__manifest__.py index 04badf6798b..f3535a7f779 100644 --- a/purchase_order_secondary_unit/__manifest__.py +++ b/purchase_order_secondary_unit/__manifest__.py @@ -8,6 +8,7 @@ "category": "Purchase", "website": "https://github.com/OCA/purchase-workflow", "author": "Tecnativa, Odoo Community Association (OCA)", + "maintainers": ["sergio-teruel"], "license": "AGPL-3", "application": False, "installable": True, diff --git a/purchase_order_secondary_unit/models/product_supplierinfo.py b/purchase_order_secondary_unit/models/product_supplierinfo.py index e5c3ad7e781..fcfddb1d7ef 100644 --- a/purchase_order_secondary_unit/models/product_supplierinfo.py +++ b/purchase_order_secondary_unit/models/product_supplierinfo.py @@ -2,6 +2,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import api, fields, models +from odoo.tools import float_compare class ProductSupplierinfo(models.Model): @@ -30,8 +31,20 @@ def _compute_secondary_uom_price(self): @api.onchange("secondary_uom_price") def _inverse_secondary_uom_price(self): + precision = self.env["decimal.precision"].precision_get("Product Price") for rec in self: if rec.secondary_uom_id: + expected_secondary_price = rec.price * rec.secondary_uom_id.factor + if ( + rec.price + and float_compare( + rec.secondary_uom_price, + expected_secondary_price, + precision_digits=precision, + ) + == 0 + ): + continue rec.price = rec.secondary_uom_price / rec.secondary_uom_id.factor @api.onchange("product_tmpl_id", "product_id") diff --git a/purchase_order_secondary_unit/models/purchase_order.py b/purchase_order_secondary_unit/models/purchase_order.py index 37cece5901e..e80edebc74a 100644 --- a/purchase_order_secondary_unit/models/purchase_order.py +++ b/purchase_order_secondary_unit/models/purchase_order.py @@ -1,6 +1,7 @@ # Copyright 2018 Tecnativa - Sergio Teruel # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import api, fields, models +from odoo.tools import float_compare class PurchaseOrder(models.Model): @@ -58,8 +59,20 @@ def _compute_secondary_uom_price(self): @api.onchange("secondary_uom_price") def _inverse_secondary_uom_price(self): + precision = self.env["decimal.precision"].precision_get("Product Price") for rec in self: if rec.secondary_uom_id: + expected_secondary_price = rec.price_unit * rec.secondary_uom_id.factor + if ( + rec.price_unit + and float_compare( + rec.secondary_uom_price, + expected_secondary_price, + precision_digits=precision, + ) + == 0 + ): + continue rec.price_unit = rec.secondary_uom_price / rec.secondary_uom_id.factor @api.onchange("product_uom") diff --git a/purchase_order_secondary_unit/static/description/index.html b/purchase_order_secondary_unit/static/description/index.html index 21fc85d2466..f74d891a0c2 100644 --- a/purchase_order_secondary_unit/static/description/index.html +++ b/purchase_order_secondary_unit/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Purchase Order Secondary Unit -
+
+

Purchase Order Secondary Unit

- - -Odoo Community Association - -
-

Purchase Order Secondary Unit

-

Beta License: AGPL-3 OCA/purchase-workflow Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/purchase-workflow Translate me on Weblate Try me on Runboat

This module extends the functionality of purchase orders to allow buy products in secondary unit of distinct category.

Users can enter quantities and prices in secondary units on purchase @@ -398,13 +393,13 @@

Purchase Order Secondary Unit

-

Configuration

+

Configuration

For configuration of displaying secondary unit information in purchase reports and the Purchase Order portal, see the guidelines provided in product_secondary_unit.

-

Usage

+

Usage

To use this module you need to:

  1. Go to a Product > General Information tab.
  2. @@ -426,14 +421,14 @@

    Usage

-

Known issues / Roadmap

+

Known issues / Roadmap

Updating existing vendor pricelist records from purchase order confirmation does not currently support secondary UOM or secondary UOM pricing. This is not included in the current scope and may be considered in future improvements.

-

Bug Tracker

+

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 @@ -441,15 +436,15 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Tecnativa
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -474,11 +469,12 @@

Maintainers

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.

+

Current maintainer:

+

sergio-teruel

This module is part of the OCA/purchase-workflow project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

-
diff --git a/purchase_order_secondary_unit/tests/test_product_supplierinfo_secondary_unit.py b/purchase_order_secondary_unit/tests/test_product_supplierinfo_secondary_unit.py index 8fc9ef7921c..0bf888a552d 100644 --- a/purchase_order_secondary_unit/tests/test_product_supplierinfo_secondary_unit.py +++ b/purchase_order_secondary_unit/tests/test_product_supplierinfo_secondary_unit.py @@ -30,6 +30,26 @@ def test_supplierinfo_secondary_uom_price_inverse(self): # secondary_uom_price = 140, factor = 0.7, price = 200 self.assertEqual(self.supplierinfo.price, 200.0) + def test_supplierinfo_keeps_price_when_secondary_price_rounds(self): + secondary_unit = self.env["product.secondary.unit"].create( + { + "name": "unit-900", + "uom_id": self.product_uom_unit.id, + "factor": 0.9, + "product_tmpl_id": self.product.product_tmpl_id.id, + } + ) + supplierinfo_form = Form( + self.env["product.supplierinfo"].with_context( + default_product_tmpl_id=self.product.product_tmpl_id.id + ) + ) + supplierinfo_form.partner_id = self.partner + supplierinfo_form.price = 19.95 + supplierinfo_form.secondary_uom_id = secondary_unit + self.assertAlmostEqual(supplierinfo_form.secondary_uom_price, 17.96) + self.assertAlmostEqual(supplierinfo_form.price, 19.95) + def test_supplierinfo_no_secondary_unit(self): self.supplierinfo.secondary_uom_id = False self.assertEqual(self.supplierinfo.secondary_uom_price, 0.0) diff --git a/purchase_order_secondary_unit/tests/test_purchase_order_secondary_unit.py b/purchase_order_secondary_unit/tests/test_purchase_order_secondary_unit.py index 9ff99cfc2b8..d6b8d9fc540 100644 --- a/purchase_order_secondary_unit/tests/test_purchase_order_secondary_unit.py +++ b/purchase_order_secondary_unit/tests/test_purchase_order_secondary_unit.py @@ -65,6 +65,32 @@ def test_purchase_order_secondary_uom_price(self): # price_unit = 100, factor = 0.7, secondary_uom_price = 70 self.assertEqual(line.secondary_uom_price, 70.0) + def test_purchase_order_keeps_supplier_price_when_secondary_price_rounds(self): + secondary_unit = self.env["product.secondary.unit"].create( + { + "name": "unit-900", + "uom_id": self.product_uom_unit.id, + "factor": 0.9, + "product_tmpl_id": self.product.product_tmpl_id.id, + } + ) + self.product.purchase_secondary_uom_id = secondary_unit + self.env["product.supplierinfo"].create( + { + "partner_id": self.partner.id, + "product_tmpl_id": self.product.product_tmpl_id.id, + "price": 19.95, + } + ) + purchase_order = Form(self.purchase_order_obj) + purchase_order.partner_id = self.partner + with purchase_order.order_line.new() as line: + line.product_id = self.product + self.assertEqual(line.secondary_uom_qty, 1.0) + self.assertAlmostEqual(line.product_qty, 0.9) + self.assertAlmostEqual(line.secondary_uom_price, 17.96) + self.assertAlmostEqual(line.price_unit, 19.95) + def test_purchase_order_confirm_creates_supplierinfo_with_secondary_uom(self): new_product = self.env["product.product"].create( {