From 7c7a0f64d3e73180b40f071ea6757f5ca1909d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Thu, 15 Jan 2026 17:20:32 +0100 Subject: [PATCH] Do not convert input when converting between the same unit Applying the factor/rate twice may loose precision if one of the number is a rational with recurring decimal. Closes #14501 (grafted from 09e3674f3ffe90f74cf2bc8a9884e885b28c93b8) --HG-- branch : 6.0 --- modules/product/tests/test_product.py | 2 + modules/product/uom.py | 72 +++++++++++++++------------ 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/modules/product/tests/test_product.py b/modules/product/tests/test_product.py index 1a163ba109..69dd2c172d 100644 --- a/modules/product/tests/test_product.py +++ b/modules/product/tests/test_product.py @@ -131,6 +131,7 @@ class ProductTestCase(CompanyTestMixin, ModuleTestCase): tests = [ ('Kilogram', 100, 'Gram', 100000, 100000), ('Gram', 1, 'Pound', 0.0022046226218487759, 0.0), + ('Ounce', 1 / 7, 'Ounce', 1 / 7, 0.14), ('Second', 5, 'Minute', 0.083333333333333343, 0.08), ('Second', 25, 'Hour', 0.0069444444444444441, 0.01), ('Millimeter', 3, 'Inch', 0.11811023622047245, 0.12), @@ -202,6 +203,7 @@ class ProductTestCase(CompanyTestMixin, ModuleTestCase): tests = [ ('Kilogram', Decimal('100'), 'Gram', Decimal('0.1')), ('Gram', Decimal('1'), 'Pound', Decimal('453.59237')), + ('Ounce', Decimal(1 / 7), 'Ounce', Decimal(1 / 7)), ('Second', Decimal('5'), 'Minute', Decimal('300')), ('Second', Decimal('25'), 'Hour', Decimal('90000')), ('Millimeter', Decimal('3'), 'Inch', Decimal('76.2')), diff --git a/modules/product/uom.py b/modules/product/uom.py index e07c8c476e..f79f4d2d0e 100644 --- a/modules/product/uom.py +++ b/modules/product/uom.py @@ -205,25 +205,28 @@ class Uom(SymbolMixin, DeactivableMixin, ModelSQL, ModelView): elif factor or rate: raise ValueError("factor and rate not allowed for same category") - if from_uom.accurate_field == 'factor': - amount = qty * from_uom.factor - else: - amount = qty / from_uom.rate - - if factor and rate: - if _accurate_operator(factor, rate) == 'rate': - factor = None + if from_uom != to_uom: + if from_uom.accurate_field == 'factor': + amount = qty * from_uom.factor else: - rate = None - if factor: - amount *= factor - elif rate: - amount /= rate + amount = qty / from_uom.rate - if to_uom.accurate_field == 'factor': - amount = amount / to_uom.factor + if factor and rate: + if _accurate_operator(factor, rate) == 'rate': + factor = None + else: + rate = None + if factor: + amount *= factor + elif rate: + amount /= rate + + if to_uom.accurate_field == 'factor': + amount = amount / to_uom.factor + else: + amount = amount * to_uom.rate else: - amount = amount * to_uom.rate + amount = qty if round: amount = to_uom.round(amount) @@ -253,27 +256,30 @@ class Uom(SymbolMixin, DeactivableMixin, ModelSQL, ModelView): elif factor or rate: raise ValueError("factor and rate not allow for same category") - format_ = '%%.%df' % uom_conversion_digits[1] + if from_uom != to_uom: + format_ = '%%.%df' % uom_conversion_digits[1] - if from_uom.accurate_field == 'factor': - new_price = price / Decimal(format_ % from_uom.factor) - else: - new_price = price * Decimal(format_ % from_uom.rate) - - if factor and rate: - if _accurate_operator(factor, rate) == 'rate': - factor = None + if from_uom.accurate_field == 'factor': + new_price = price / Decimal(format_ % from_uom.factor) else: - rate = None - if factor: - new_price /= Decimal(factor) - elif rate: - new_price *= Decimal(rate) + new_price = price * Decimal(format_ % from_uom.rate) - if to_uom.accurate_field == 'factor': - new_price = new_price * Decimal(format_ % to_uom.factor) + if factor and rate: + if _accurate_operator(factor, rate) == 'rate': + factor = None + else: + rate = None + if factor: + new_price /= Decimal(factor) + elif rate: + new_price *= Decimal(rate) + + if to_uom.accurate_field == 'factor': + new_price = new_price * Decimal(format_ % to_uom.factor) + else: + new_price = new_price / Decimal(format_ % to_uom.rate) else: - new_price = new_price / Decimal(format_ % to_uom.rate) + new_price = price return new_price