diff options
| author | Xavier Morel <xmo@odoo.com> | 2017-05-17 16:50:52 +0200 |
|---|---|---|
| committer | Xavier Morel <xmo@odoo.com> | 2017-07-18 11:49:28 +0200 |
| commit | ace4ee39e81a6da9e94236b5746f9ee70f8c9b95 (patch) | |
| tree | 086ce01febb306811755745f337af3014fcb9be1 | |
| parent | dc2575b5db2aaa2c56457cbd0848a02406260ff6 (diff) | |
[FIX] P3: round() compatibility shim
In Python 3 round:
* rounds half to even (banker's rounding) rather than up
* returns an int if not given a precision (or given a precision of
None)
* discards the negative sign when rounding to 0
Add a compatibility shim which implements P2 behaviour and at least
allows passing float_utils's tests.
Implementing float_round on top of Decimal was attempted but did not
work: although Decimal.quantize takes a non-integral
Decimal (e.g. ``d.quantize(D('0.001'))``) it only supports integral
powers of 10 and does not support quantizing to e.g. 0.05: while that
will not fail it will just use the exponent for quantization and 0.05
is thus equivalent to 0.01, which is not what we want if e.g. need to
round a value to 5 cents.
This is hinted at but maybe not clearly spelled out by the official
documentation:
> Return a value equal to the first operand after rounding
> and **having the exponent of** the second operand.
Also convert a bunch of hand-rolled currency rounding to just using
the round() method on res.currency.
| -rw-r--r-- | addons/account/models/account_analytic_line.py | 2 | ||||
| -rw-r--r-- | addons/account/models/account_invoice.py | 9 | ||||
| -rw-r--r-- | addons/purchase/models/account_invoice.py | 7 | ||||
| -rw-r--r-- | addons/resource/models/resource_mixin.py | 6 | ||||
| -rw-r--r-- | addons/stock_account/models/account_invoice.py | 2 | ||||
| -rw-r--r-- | odoo/tools/float_utils.py | 2 | ||||
| -rw-r--r-- | odoo/tools/pycompat.py | 17 |
7 files changed, 30 insertions, 15 deletions
diff --git a/addons/account/models/account_analytic_line.py b/addons/account/models/account_analytic_line.py index 20b44c04dbb..1d212d31bf8 100644 --- a/addons/account/models/account_analytic_line.py +++ b/addons/account/models/account_analytic_line.py @@ -44,7 +44,7 @@ class AccountAnalyticLine(models.Model): # Compute based on pricetype amount_unit = self.product_id.price_compute('standard_price', uom=unit)[self.product_id.id] amount = amount_unit * self.unit_amount or 0.0 - result = round(amount, self.currency_id.decimal_places) * -1 + result = self.currency_id.round(amount) * -1 self.amount = result self.general_account_id = account self.product_uom_id = unit diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 24e8abc3049..37ec02186e9 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1551,14 +1551,13 @@ class AccountPaymentTerm(models.Model): currency = self.env['res.currency'].browse(self.env.context['currency_id']) else: currency = self.env.user.company_id.currency_id - prec = currency.decimal_places for line in self.line_ids: if line.value == 'fixed': - amt = round(line.value_amount, prec) + amt = currency.round(line.value_amount) elif line.value == 'percent': - amt = round(value * (line.value_amount / 100.0), prec) + amt = currency.round(value * (line.value_amount / 100.0)) elif line.value == 'balance': - amt = round(amount, prec) + amt = currency.round(amount) if amt: next_date = fields.Date.from_string(date_ref) if line.option == 'day_after_invoice_date': @@ -1573,7 +1572,7 @@ class AccountPaymentTerm(models.Model): result.append((fields.Date.to_string(next_date), amt)) amount -= amt amount = sum(amt for _, amt in result) - dist = round(value - amount, prec) + dist = currency.round(value - amount) if dist: last_date = result and result[-1][0] or fields.Date.today() result.append((last_date, dist)) diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py index c1c62f32da6..350e8ad8684 100644 --- a/addons/purchase/models/account_invoice.py +++ b/addons/purchase/models/account_invoice.py @@ -146,7 +146,6 @@ class AccountInvoice(models.Model): # reference_account_id is the stock input account reference_account_id = i_line.product_id.product_tmpl_id.get_product_accounts(fiscal_pos=fpos)['stock_input'].id diff_res = [] - account_prec = inv.company_id.currency_id.decimal_places # calculate and write down the possible price difference between invoice price and product price for line in res: if line.get('invl_id', 0) == i_line.id and reference_account_id == line['account_id']: @@ -180,13 +179,13 @@ class AccountInvoice(models.Model): if child.type_tax_use != 'none': tax_ids.append((4, child.id, None)) price_before = line.get('price', 0.0) - line.update({'price': round(valuation_price_unit * line['quantity'], account_prec)}) + line.update({'price': company_currency.round(valuation_price_unit * line['quantity'])}) diff_res.append({ 'type': 'src', 'name': i_line.name[:64], - 'price_unit': round(price_unit - valuation_price_unit, account_prec), + 'price_unit': company_currency.round(price_unit - valuation_price_unit), 'quantity': line['quantity'], - 'price': round(price_before - line.get('price', 0.0), account_prec), + 'price': company_currency.round(price_before - line.get('price', 0.0)), 'account_id': acc, 'product_id': line['product_id'], 'uom_id': line['uom_id'], diff --git a/addons/resource/models/resource_mixin.py b/addons/resource/models/resource_mixin.py index 8b922dd5ff8..0d45ec646a1 100644 --- a/addons/resource/models/resource_mixin.py +++ b/addons/resource/models/resource_mixin.py @@ -4,7 +4,7 @@ from datetime import timedelta from odoo import api, fields, models - +from odoo.tools import pycompat class ResourceMixin(models.AbstractModel): _name = "resource.mixin" @@ -47,7 +47,7 @@ class ResourceMixin(models.AbstractModel): theoric_hours = self.get_day_work_hours_count(day_intervals[0][0].date(), calendar=calendar) work_time = sum((interval[1] - interval[0] for interval in day_intervals), timedelta()) total_work_time += work_time - days_count += round((work_time.total_seconds() / 3600 / theoric_hours) * 4) / 4 + days_count += pycompat.round((work_time.total_seconds() / 3600 / theoric_hours) * 4) / 4 return { 'days': days_count, 'hours': total_work_time.total_seconds() / 3600, @@ -70,7 +70,7 @@ class ResourceMixin(models.AbstractModel): for day_intervals in calendar._iter_leave_intervals(from_datetime, to_datetime, self.resource_id.id): theoric_hours = self.get_day_work_hours_count(day_intervals[0][0].date(), calendar=calendar) leave_time = sum((interval[1] - interval[0] for interval in day_intervals), timedelta()) - days_count += round((leave_time.total_seconds()/3600 / theoric_hours) * 4) / 4 + days_count += pycompat.round((leave_time.total_seconds() / 3600 / theoric_hours) * 4) / 4 return days_count def iter_leaves(self, from_datetime, to_datetime, calendar=None): diff --git a/addons/stock_account/models/account_invoice.py b/addons/stock_account/models/account_invoice.py index 6291da2922f..281d15de0e3 100644 --- a/addons/stock_account/models/account_invoice.py +++ b/addons/stock_account/models/account_invoice.py @@ -94,7 +94,7 @@ class AccountInvoiceLine(models.Model): price = company_currency.with_context(date=self.invoice_id.date_invoice).compute(price_unit * self.quantity, self.invoice_id.currency_id) else: price = price_unit * self.quantity - return round(price, self.invoice_id.currency_id.decimal_places) + return self.invoice_id.currency_id.round(price) def get_invoice_line_account(self, type, product, fpos, company): if company.anglo_saxon_accounting and type in ('in_invoice', 'in_refund') and product and product.type == 'product': diff --git a/odoo/tools/float_utils.py b/odoo/tools/float_utils.py index 9c37da07fe9..d71576adc92 100644 --- a/odoo/tools/float_utils.py +++ b/odoo/tools/float_utils.py @@ -58,7 +58,7 @@ def float_round(value, precision_digits=None, precision_rounding=None, rounding_ epsilon = 2**(epsilon_magnitude-53) if rounding_method == 'HALF-UP': normalized_value += math.copysign(epsilon, normalized_value) - rounded_value = round(normalized_value) # round to integer + rounded_value = pycompat.round(normalized_value) # round to integer # TIE-BREAKING: UP (for ceiling operations) # When rounding the value up, we instead subtract the epsilon value diff --git a/odoo/tools/pycompat.py b/odoo/tools/pycompat.py index 7ce4f45773a..19b821863b2 100644 --- a/odoo/tools/pycompat.py +++ b/odoo/tools/pycompat.py @@ -8,6 +8,7 @@ PY2 = sys.version_info[0] == 2 if PY2: # pylint: disable=long-builtin,dict-iter-method integer_types = (int, long) + round = round keys = lambda d: iter(d.iterkeys()) values = lambda d: iter(d.itervalues()) @@ -23,8 +24,24 @@ if PY2: exec ('def reraise(tp, value, tb=None):\n raise tp, value, tb') else: + import builtins, math # pylint: disable=bad-functions integer_types = (int,) + def round(f): + # P3's builtin round differs from P2 in the following manner: + # * it rounds half to even rather than up (away from 0) + # * round(-0.) loses the sign (it returns -0 rather than 0) + # * round(x) returns an int rather than a float + # + # this compatibility shim implements Python 2's round in terms of + # Python 3's so that important rounding error under P3 can be + # trivially fixed, assuming the P2 behaviour to be debugged and + # correct. + roundf = builtins.round(f) + if builtins.round(f + 1) - roundf != 1: + return f + math.copysign(0.5, f) + # copysign ensures round(-0.) -> -0 *and* result is a float + return math.copysign(roundf, f) keys = lambda d: iter(d.keys()) values = lambda d: iter(d.values()) |
