[MERGE] forward port of branch 7.0 up to 3a0af6a
authorDenis Ledoux <dle@odoo.com>
Wed, 22 Oct 2014 17:26:27 +0000 (19:26 +0200)
committerDenis Ledoux <dle@odoo.com>
Wed, 22 Oct 2014 17:26:27 +0000 (19:26 +0200)
addons/product/_common.py
addons/product/product.py
addons/product/product_view.xml
openerp/addons/base/test/base_test.yml
openerp/osv/orm.py
openerp/report/render/rml2pdf/trml2pdf.py
openerp/tools/float_utils.py

index c05dcee..f44f6b1 100644 (file)
@@ -20,9 +20,6 @@
 ##############################################################################
 from openerp import tools
 
-import math
-
-
 def rounding(f, r):
        # TODO for trunk: log deprecation warning
        # _logger.warning("Deprecated rounding method, please use tools.float_round to round floats.")
@@ -32,4 +29,4 @@ def rounding(f, r):
 def ceiling(f, r):
     if not r:
         return f
-    return math.ceil(f / r) * r
+    return tools.float_round(f, precision_rounding=r, rounding_method='UP')
index dd117e4..38bb85e 100644 (file)
@@ -132,10 +132,10 @@ class product_uom(osv.osv):
         'name': fields.char('Unit of Measure', required=True, translate=True),
         'category_id': fields.many2one('product.uom.categ', 'Category', required=True, ondelete='cascade',
             help="Conversion between Units of Measure can only occur if they belong to the same category. The conversion will be made based on the ratios."),
-        'factor': fields.float('Ratio', required=True,digits=(12, 12),
+        'factor': fields.float('Ratio', required=True, digits=0, # force NUMERIC with unlimited precision
             help='How much bigger or smaller this unit is compared to the reference Unit of Measure for this category:\n'\
                     '1 * (reference unit) = ratio * (this unit)'),
-        'factor_inv': fields.function(_factor_inv, digits=(12,12),
+        'factor_inv': fields.function(_factor_inv, digits=0, # force NUMERIC with unlimited precision
             fnct_inv=_factor_inv_write,
             string='Bigger Ratio',
             help='How many times this Unit of Measure is bigger than the reference Unit of Measure in this category:\n'\
index 7260d4a..666622f 100644 (file)
                             <field name="uom_type" on_change="onchange_type(uom_type)"/>
                             <label for="factor"/>
                             <div>
-                                <field name="factor" attrs="{'invisible':[('uom_type','!=','smaller')]}"/>
-                                <field name="factor_inv" attrs="{'invisible':[('uom_type','!=','bigger')]}"/>
+                                <field name="factor"
+                                    digits="[42,5]"
+                                    attrs="{'invisible':[('uom_type','!=','smaller')],
+                                            'readonly':[('uom_type','!=','smaller')]}"/>
+                                <field name="factor_inv"
+                                    digits="[42,5]"
+                                    attrs="{'invisible':[('uom_type','!=','bigger')],
+                                            'readonly':[('uom_type','!=','bigger')]}"/>
                                 <p attrs="{'invisible':[('uom_type','!=','smaller')]}" class="oe_grey">
                                     e.g: 1 * (reference unit) = ratio * (this unit)
                                 </p>
index e73d817..3a4ccf1 100644 (file)
     !python {model: res.currency}: |
         from openerp.tools import float_compare, float_is_zero, float_round, float_repr
         def try_round(amount, expected, precision_digits=3, float_round=float_round, float_repr=float_repr):
-            result = float_repr(float_round(amount, precision_digits=precision_digits),
-                                precision_digits=precision_digits)
+            result = float_repr(float_round(amount, precision_digits=precision_digits, rounding_method=rounding_method),
             assert result == expected, 'Rounding error: got %s, expected %s' % (result, expected)
         try_round(2.6745, '2.675')
         try_round(-2.6745, '-2.675')
         try_round(457.4554, '457.455')
         try_round(-457.4554, '-457.455')
 
+        # Try some rounding value with rounding method UP instead of HALF-UP
+        # We use 8.175 because when normalizing 8.175 with precision_digits=3 it gives
+        # us 8175,0000000001234 as value, and if not handle correctly the rounding UP
+        # value will be incorrect (should be 8,175 and not 8,176)
+        try_round(8.175, '8.175', rounding_method='UP')
+        try_round(8.1751, '8.176', rounding_method='UP')
+        try_round(-8.175, '-8.175', rounding_method='UP')
+        try_round(-8.1751, '-8.176', rounding_method='UP')
+        try_round(-6.000, '-6.000', rounding_method='UP')
+        try_round(1.8, '2', 0, rounding_method='UP')
+        try_round(-1.8, '-2', 0, rounding_method='UP')
+
         # Extended float range test, inspired by Cloves Almeida's test on bug #882036.
         fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555]
         expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556']
index bcec1ac..62ef9eb 100644 (file)
@@ -597,7 +597,12 @@ def get_pg_type(f, type_override=None):
     if field_type in FIELDS_TO_PGTYPES:
         pg_type =  (FIELDS_TO_PGTYPES[field_type], FIELDS_TO_PGTYPES[field_type])
     elif issubclass(field_type, fields.float):
-        if f.digits:
+        # Explicit support for "falsy" digits (0, False) to indicate a
+        # NUMERIC field with no fixed precision. The values will be saved
+        # in the database with all significant digits.
+        # FLOAT8 type is still the default when there is no precision because
+        # it is faster for most operations (sums, etc.)
+        if f.digits is not None:
             pg_type = ('numeric', 'NUMERIC')
         else:
             pg_type = ('float8', 'DOUBLE PRECISION')
index 61eedaf..dab7ee5 100644 (file)
@@ -91,7 +91,6 @@ class NumberedCanvas(canvas.Canvas):
         self._saved_page_states = []
 
     def showPage(self):
-        self._saved_page_states.append(dict(self.__dict__))
         self._startPage()
 
     def save(self):
@@ -126,6 +125,8 @@ class PageCount(platypus.Flowable):
 
 class PageReset(platypus.Flowable):
     def draw(self):
+        """Flag to close current story page numbering and prepare for the next
+        should be executed after the rendering of the full story"""
         self.canv._doPageReset = True
 
 class _rml_styles(object,):
@@ -936,6 +937,9 @@ class TinyDocTemplate(platypus.BaseDocTemplate):
         self.handle_frameBegin()
 
     def afterPage(self):
+        if isinstance(self.canv, NumberedCanvas):
+            # save current page states before eventual reset
+            self.canv._saved_page_states.append(dict(self.canv.__dict__))
         if self.canv._doPageReset:
             # Following a <pageReset/> tag:
             # - we reset page number to 0
@@ -1009,10 +1013,10 @@ class _rml_template(object):
         story_cnt = 0
         for node_story in node_stories:
             if story_cnt > 0:
-                # Reset Page Number with new story tag
-                fis.append(PageReset())
                 fis.append(platypus.PageBreak())
             fis += r.render(node_story)
+            # end of story numbering computation
+            fis.append(PageReset())
             story_cnt += 1
         try:
             if self.localcontext and self.localcontext.get('internal_header',False):
index 5c93411..9f7b499 100644 (file)
@@ -29,10 +29,11 @@ def _float_check_precision(precision_digits=None, precision_rounding=None):
         return 10 ** -precision_digits
     return precision_rounding
 
-def float_round(value, precision_digits=None, precision_rounding=None):
-    """Return ``value`` rounded to ``precision_digits``
-       decimal digits, minimizing IEEE-754 floating point representation
-       errors, and applying HALF-UP (away from zero) tie-breaking rule.
+def float_round(value, precision_digits=None, precision_rounding=None, rounding_method='HALF-UP'):
+    """Return ``value`` rounded to ``precision_digits`` decimal digits,
+       minimizing IEEE-754 floating point representation errors, and applying
+       the tie-breaking rule selected with ``rounding_method``, by default
+       HALF-UP (away from zero).
        Precision must be given by ``precision_digits`` or ``precision_rounding``,
        not both!
 
@@ -41,6 +42,9 @@ def float_round(value, precision_digits=None, precision_rounding=None):
        :param float precision_rounding: decimal number representing the minimum
            non-zero value at the desired precision (for example, 0.01 for a 
            2-digit precision).
+       :param rounding_method: the rounding method used: 'HALF-UP' or 'UP', the first
+           one rounding up to the closest number with the rule that number>=0.5 is 
+           rounded up to 1, and the latest one always rounding up.
        :return: rounded float
     """
     rounding_factor = _float_check_precision(precision_digits=precision_digits,
@@ -52,7 +56,7 @@ def float_round(value, precision_digits=None, precision_rounding=None):
     # we normalize the value before rounding it as an integer, and de-normalize
     # after rounding: e.g. float_round(1.3, precision_rounding=.5) == 1.5
 
-    # TIE-BREAKING: HALF-UP
+    # TIE-BREAKING: HALF-UP (for normal rounding)
     # We want to apply HALF-UP tie-breaking rules, i.e. 0.5 rounds away from 0.
     # Due to IEE754 float/double representation limits, the approximation of the
     # real value may be slightly below the tie limit, resulting in an error of
@@ -66,8 +70,23 @@ def float_round(value, precision_digits=None, precision_rounding=None):
     normalized_value = value / rounding_factor # normalize
     epsilon_magnitude = math.log(abs(normalized_value), 2)
     epsilon = 2**(epsilon_magnitude-53)
-    normalized_value += cmp(normalized_value,0) * epsilon
-    rounded_value = round(normalized_value) # round to integer
+    if rounding_method == 'HALF-UP':
+        normalized_value += cmp(normalized_value,0) * epsilon
+        rounded_value = round(normalized_value) # round to integer
+
+    # TIE-BREAKING: UP (for ceiling operations)
+    # When rounding the value up, we instead subtract the epsilon value
+    # as the the approximation of the real value may be slightly *above* the
+    # tie limit, this would result in incorrectly rounding up to the next number
+    # The math.ceil operation is applied on the absolute value in order to
+    # round "away from zero" and not "towards infinity", then the sign is
+    # restored.
+
+    elif rounding_method == 'UP':
+        sign = cmp(normalized_value, 0)
+        normalized_value -= sign*epsilon
+        rounded_value = math.ceil(abs(normalized_value))*sign # ceil to integer
+
     result = rounded_value * rounding_factor # de-normalize
     return result