From 1ba91042d673cde921f9d47884866db6f9174b7d Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 22 Jun 2016 16:06:09 +0200 Subject: [PATCH] [FIX] fields: do not serialize more digits than necessary in `monetary._symbol_set()` Add a class for float objects that carry their precision digits, use it for the value of monetary fields in record cache, and serialize them as expected for the database. --- openerp/addons/test_new_api/models.py | 3 +++ .../test_new_api/tests/test_new_fields.py | 16 ++++++++++++++++ openerp/fields.py | 5 +++-- openerp/osv/fields.py | 8 +++++++- openerp/tools/float_utils.py | 19 +++++++++++++++++++ 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/openerp/addons/test_new_api/models.py b/openerp/addons/test_new_api/models.py index e1c10626e94..b6920b793af 100644 --- a/openerp/addons/test_new_api/models.py +++ b/openerp/addons/test_new_api/models.py @@ -264,6 +264,9 @@ class MixedModel(models.Model): comment3 = fields.Html(sanitize=True, strip_classes=True) comment4 = fields.Html(sanitize=True, strip_style=True) + currency_id = fields.Many2one('res.currency', default=lambda self: self.env.ref('base.EUR')) + amount = fields.Monetary() + @api.one def _compute_now(self): # this is a non-stored computed field without dependencies diff --git a/openerp/addons/test_new_api/tests/test_new_fields.py b/openerp/addons/test_new_api/tests/test_new_fields.py index ba1bbc27248..22a8de46332 100644 --- a/openerp/addons/test_new_api/tests/test_new_fields.py +++ b/openerp/addons/test_new_api/tests/test_new_fields.py @@ -235,6 +235,22 @@ class TestNewFields(common.TransactionCase): record.number = 2.4999999999999996 self.assertEqual(record.number, 2.50) + def test_20_monetary(self): + """ test monetary fields """ + record = self.env['test_new_api.mixed'].create({}) + self.assertTrue(record.currency_id) + self.assertEqual(record.currency_id.rounding, 0.01) + + # the conversion to cache should round the value to 14.700000000000001 + record.amount = 14.7 + self.assertNotEqual(record.amount, 14.7) + self.assertEqual(record.amount, 14.700000000000001) + + # however when stored to database, it should be serialized as 14.70 + self.cr.execute('SELECT amount FROM test_new_api_mixed WHERE id=%s', (record.id,)) + (amount,) = self.cr.fetchone() + self.assertEqual(amount, 14.7) + def test_21_date(self): """ test date fields """ record = self.env['test_new_api.mixed'].create({}) diff --git a/openerp/fields.py b/openerp/fields.py index e69fc2d0d04..8e995a8f344 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -13,7 +13,7 @@ import pytz import xmlrpclib from openerp.sql_db import LazyCursor -from openerp.tools import float_round, frozendict, html_sanitize, ustr, OrderedSet +from openerp.tools import float_precision, float_round, frozendict, html_sanitize, ustr, OrderedSet from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT from openerp.tools.translate import html_translate @@ -1177,7 +1177,8 @@ class Monetary(Field): # FIXME @rco-odoo: currency may not be already initialized if it is # a function or related field! if currency: - return currency.round(float(value or 0.0)) + value = currency.round(float(value or 0.0)) + return float_precision(value, currency.decimal_places) return float(value or 0.0) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index a15d55201b1..413bea7bea0 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -404,9 +404,15 @@ class float(_column): def digits_change(self, cr): pass +def _symbol_set_monetary(val): + try: + return val.float_repr() # see float_precision.float_repr() + except Exception: + return __builtin__.float(val or 0.0) + class monetary(_column): _type = 'monetary' - _symbol_set = ('%s', lambda x: __builtin__.float(x or 0.0)) + _symbol_set = ('%s', _symbol_set_monetary) _symbol_get = lambda self,x: x or 0.0 def to_field_args(self): diff --git a/openerp/tools/float_utils.py b/openerp/tools/float_utils.py index 8614e043692..67777145dba 100644 --- a/openerp/tools/float_utils.py +++ b/openerp/tools/float_utils.py @@ -148,6 +148,25 @@ def float_repr(value, precision_digits): # precision. e.g. str(123456789.1234) == str(123456789.123)!! return ("%%.%sf" % precision_digits) % value +_float_repr = float_repr + +class float_precision(float): + """ A class for float values that carry precision digits. This is a thin + layer on top of ``float``, and the precision digits are not propagated + to the result of arithmetic operations. This class is used when + converting monetary values to cache, and for serializing them to the + database. + """ + __slots__ = ['precision_digits'] + + def __new__(cls, value, precision_digits): + obj = super(float_precision, cls).__new__(cls, value) + obj.precision_digits = precision_digits + return obj + + def float_repr(self): + return _float_repr(self, self.precision_digits) + if __name__ == "__main__":