From d2147fc92bb808b9adb72e432b84ebcd6d0b0192 Mon Sep 17 00:00:00 2001 From: David Monjoie Date: Tue, 21 Oct 2014 14:57:52 +0200 Subject: [PATCH 01/10] [IMP] web_graph: renamed Quantity field into Count to avoid ambiguities --- addons/web_graph/static/src/js/graph_widget.js | 4 ++-- addons/web_graph/static/src/js/pivot_table.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/web_graph/static/src/js/graph_widget.js b/addons/web_graph/static/src/js/graph_widget.js index bf6df084fbc..350cd3ff34a 100644 --- a/addons/web_graph/static/src/js/graph_widget.js +++ b/addons/web_graph/static/src/js/graph_widget.js @@ -46,13 +46,13 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ context: self.graph_view.dataset.context }).then(function (f) { self.fields = f; - self.fields.__count = {field:'__count', type: 'integer', string:_t('Quantity')}; + self.fields.__count = {field:'__count', type: 'integer', string:_t('Count')}; self.important_fields = self.get_search_fields(); self.measure_list = self.get_measures(); self.add_measures_to_options(); self.pivot_options.row_groupby = self.create_field_values(self.pivot_options.row_groupby || []); self.pivot_options.col_groupby = self.create_field_values(self.pivot_options.col_groupby || []); - self.pivot_options.measures = self.create_field_values(self.pivot_options.measures || [{field:'__count', type: 'integer', string:'Quantity'}]); + self.pivot_options.measures = self.create_field_values(self.pivot_options.measures || [{field:'__count', type: 'integer', string:'Count'}]); self.pivot = new openerp.web_graph.PivotTable(self.model, self.domain, self.fields, self.pivot_options); self.pivot.update_data().then(function () { self.display_data(); diff --git a/addons/web_graph/static/src/js/pivot_table.js b/addons/web_graph/static/src/js/pivot_table.js index 2d68cb43932..0f10cf82dd6 100644 --- a/addons/web_graph/static/src/js/pivot_table.js +++ b/addons/web_graph/static/src/js/pivot_table.js @@ -17,7 +17,7 @@ openerp.web_graph.PivotTable = openerp.web.Class.extend({ this.no_data = true; this.model = model; this.fields = fields; - this.fields.__count = {type: 'integer', string:_t('Quantity')}; + this.fields.__count = {type: 'integer', string:_t('Count')}; this.measures = options.measures || []; this.rows = { groupby: options.row_groupby, headers: null }; this.cols = { groupby: options.col_groupby, headers: null }; From 354b82bee050ed29dd96adad613cac79ca778104 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Tue, 21 Oct 2014 10:54:23 +0200 Subject: [PATCH 02/10] [FIX] report: page numbering rml reports Save the NumberedCanvas state before doing a page reset. The order of execution when rendering an rml report is the following: 1. init canevas (_pageNumber = 1) 2. render the page element 3. if still pages to render, afterPage method 4. if still pages to render, showPage method (_pageNumber += 1) 5. back to step 2 for each page 6. draw the ResetPage element (setting flag _doPageReset=True) 7. end the document build with afterPage & showPage method The PageReset element should be executed at the end of the rendering of a story (subdocument) to reinitialize the page numbers to 0 (for new story) and insert the pageCount element for that story with the total number of pages (needed if want to use tag in rml). In case of NumberedCanvas (e.g. used in Trial Balance report), the numbering is generated at the end of the build using the _saved_page_states dict in the canevas. To have an accurate _saved_page_states content, it needs to be saved before the pageReset. Fixes #2225 --- openerp/report/render/rml2pdf/trml2pdf.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openerp/report/render/rml2pdf/trml2pdf.py b/openerp/report/render/rml2pdf/trml2pdf.py index 75bc30d4a19..3b36c3ef932 100644 --- a/openerp/report/render/rml2pdf/trml2pdf.py +++ b/openerp/report/render/rml2pdf/trml2pdf.py @@ -88,7 +88,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): @@ -123,6 +122,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,): @@ -930,6 +931,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 tag: # - we reset page number to 0 @@ -1003,10 +1007,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): From 2e2778c1b1a46c49ebacc2423997d01818f6ac03 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Tue, 21 Oct 2014 16:54:56 +0200 Subject: [PATCH 03/10] [IMP] project: convert file CRLF to Unix end of line --- addons/project/security/ir.model.access.csv | 50 ++++++++++----------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv index c88fb276fcb..8f68a3ffc34 100644 --- a/addons/project/security/ir.model.access.csv +++ b/addons/project/security/ir.model.access.csv @@ -1,25 +1,25 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_project_project,project.project,model_project_project,project.group_project_user,1,0,0,0 -access_project_project_manager,project.project,model_project_project,project.group_project_manager,1,1,1,1 -access_account_analytic_account_user,account.analytic.account,analytic.model_account_analytic_account,project.group_project_user,1,0,0,0 -access_account_analytic_account,account.analytic.account,analytic.model_account_analytic_account,project.group_project_manager,1,1,1,1 -access_project_task_type_user,project.task.type user,model_project_task_type,project.group_project_user,1,1,1,1 -access_project_task,project.task,model_project_task,project.group_project_user,1,1,1,1 -access_project_task_work,project.task.work,model_project_task_work,project.group_project_user,1,1,1,1 -access_report_project_task_user,report.project.task.user,model_report_project_task_user,project.group_project_manager,1,1,1,1 -access_partner_task user,base.res.partner user,base.model_res_partner,project.group_project_user,1,0,0,0 -access_task_on_partner,project.task on partners,model_project_task,base.group_user,1,0,0,0 -access_project_on_partner,project.project on partners,model_project_project,base.group_user,1,0,0,0 -access_project_task_sale_user,project.task salesman,model_project_task,base.group_sale_salesman,1,1,1,1 -access_project_task_type_sale_user,project.task.type salesman,project.model_project_task_type,base.group_sale_salesman,1,1,1,1 -access_project_task_history_sale_user,project.task.history salesman,project.model_project_task_history,base.group_sale_salesman,1,1,1,1 -access_project_project_sale_user,project.project salesman,model_project_project,base.group_sale_salesman,1,0,0,0 -access_account_analytic_line_project,account.analytic.line project,analytic.model_account_analytic_line,project.group_project_manager,1,1,1,1 -access_project_task_history,project.task.history project,project.model_project_task_history,project.group_project_user,1,1,1,0 -access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_manager,1,0,0,0 -access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_user,1,0,0,0 -access_resource_calendar,project.resource_calendar manager,resource.model_resource_calendar,project.group_project_manager,1,0,0,0 -access_resource_calendar_leaves_user,resource.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1 -access_project_category,project.project_category,model_project_category,,1,0,0,0 -access_project_category_manager,project.project_category,model_project_category,project.group_project_manager,1,1,1,1 -access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1 +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_project_project,project.project,model_project_project,project.group_project_user,1,0,0,0 +access_project_project_manager,project.project,model_project_project,project.group_project_manager,1,1,1,1 +access_account_analytic_account_user,account.analytic.account,analytic.model_account_analytic_account,project.group_project_user,1,0,0,0 +access_account_analytic_account,account.analytic.account,analytic.model_account_analytic_account,project.group_project_manager,1,1,1,1 +access_project_task_type_user,project.task.type user,model_project_task_type,project.group_project_user,1,1,1,1 +access_project_task,project.task,model_project_task,project.group_project_user,1,1,1,1 +access_project_task_work,project.task.work,model_project_task_work,project.group_project_user,1,1,1,1 +access_report_project_task_user,report.project.task.user,model_report_project_task_user,project.group_project_manager,1,1,1,1 +access_partner_task user,base.res.partner user,base.model_res_partner,project.group_project_user,1,0,0,0 +access_task_on_partner,project.task on partners,model_project_task,base.group_user,1,0,0,0 +access_project_on_partner,project.project on partners,model_project_project,base.group_user,1,0,0,0 +access_project_task_sale_user,project.task salesman,model_project_task,base.group_sale_salesman,1,1,1,1 +access_project_task_type_sale_user,project.task.type salesman,project.model_project_task_type,base.group_sale_salesman,1,1,1,1 +access_project_task_history_sale_user,project.task.history salesman,project.model_project_task_history,base.group_sale_salesman,1,1,1,1 +access_project_project_sale_user,project.project salesman,model_project_project,base.group_sale_salesman,1,0,0,0 +access_account_analytic_line_project,account.analytic.line project,analytic.model_account_analytic_line,project.group_project_manager,1,1,1,1 +access_project_task_history,project.task.history project,project.model_project_task_history,project.group_project_user,1,1,1,0 +access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_manager,1,0,0,0 +access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_user,1,0,0,0 +access_resource_calendar,project.resource_calendar manager,resource.model_resource_calendar,project.group_project_manager,1,0,0,0 +access_resource_calendar_leaves_user,resource.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1 +access_project_category,project.project_category,model_project_category,,1,0,0,0 +access_project_category_manager,project.project_category,model_project_category,project.group_project_manager,1,1,1,1 +access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1 From 118b5073e005bcfdc37f557a4741439ffc798cd0 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Tue, 21 Oct 2014 16:56:08 +0200 Subject: [PATCH 04/10] [FIX] project: read access for project user Backport of 79bed94 (project user access to resource.calendar) and adding the access to resource.calendar.attendance. It is needed to compute function fields such as day_open (present in form view of project.issue) Fixes #3201 --- addons/project/security/ir.model.access.csv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv index 8f68a3ffc34..64813df74d9 100644 --- a/addons/project/security/ir.model.access.csv +++ b/addons/project/security/ir.model.access.csv @@ -18,7 +18,8 @@ access_account_analytic_line_project,account.analytic.line project,analytic.mode access_project_task_history,project.task.history project,project.model_project_task_history,project.group_project_user,1,1,1,0 access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_manager,1,0,0,0 access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_user,1,0,0,0 -access_resource_calendar,project.resource_calendar manager,resource.model_resource_calendar,project.group_project_manager,1,0,0,0 +access_resource_calendar,project.resource_calendar user,resource.model_resource_calendar,project.group_project_user,1,0,0,0 +access_resource_calendar_attendance,project.resource_calendar_attendance user,resource.model_resource_calendar_attendance,project.group_project_user,1,0,0,0 access_resource_calendar_leaves_user,resource.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1 access_project_category,project.project_category,model_project_category,,1,0,0,0 access_project_category_manager,project.project_category,model_project_category,project.group_project_manager,1,1,1,1 From 7705f883d23b71cdb1d30300ce69d9df665ae55e Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 8 Oct 2014 16:10:52 +0200 Subject: [PATCH 05/10] [FIX] base: support float rounding with rounding_method=UP (ceiling) Add rounding_method parameter on float_round method to offer HALF-UP (default, usual round) or UP (ceiling) rounding method. Use the second method instead of math.ceil() for product reservations. For UP, the python math.ceil() method uses "torwards infinity" rounding method while we want "away from zero". Therefore we use the absolute value of normalized_value to make sure than -1.8 is rounded to -2.0 and not -1. Fixes #1125 #2793 This is a cherry-pick of d4972ff which was reverted at 333852e due to remaining issue with negative values. --- addons/product/_common.py | 5 +--- openerp/addons/base/test/base_test.yml | 16 +++++++++++-- openerp/tools/float_utils.py | 33 ++++++++++++++++++++------ 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/addons/product/_common.py b/addons/product/_common.py index c05dcee66a2..f44f6b11a4c 100644 --- a/addons/product/_common.py +++ b/addons/product/_common.py @@ -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') diff --git a/openerp/addons/base/test/base_test.yml b/openerp/addons/base/test/base_test.yml index 11f243592fa..90c65944a25 100644 --- a/openerp/addons/base/test/base_test.yml +++ b/openerp/addons/base/test/base_test.yml @@ -198,8 +198,8 @@ - !python {model: res.currency}: | from 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), + def try_round(amount, expected, precision_digits=3, float_round=float_round, float_repr=float_repr, rounding_method='HALF-UP'): + result = float_repr(float_round(amount, precision_digits=precision_digits, rounding_method=rounding_method), precision_digits=precision_digits) assert result == expected, 'Rounding error: got %s, expected %s' % (result, expected) try_round(2.6745, '2.675') @@ -213,6 +213,18 @@ 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'] diff --git a/openerp/tools/float_utils.py b/openerp/tools/float_utils.py index 5c934114e3c..9f7b49974f4 100644 --- a/openerp/tools/float_utils.py +++ b/openerp/tools/float_utils.py @@ -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 From fa2f7b86bff9cecad73ef14fdc1480c7bca2a584 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Thu, 16 Oct 2014 17:23:51 +0200 Subject: [PATCH 06/10] [FIX] product: remove digits_precision from uom factor fields Remove the hardcoded precision of 12 on factor and factor_inv, to use the complete natural precision of NUMERIC types, preserving all significant digits. e.g. a UoM with a factor_inv of 6.0 used to be computed as: factor_inv: 6.0 -> factor: 0.166666666667 (1.0/6.0, rounded to 12 digits) -> factor_inv: 5.999999999988 (1.0/factor) which could lead to errors such 12*0.166666666667 = 2.000000000004 instead of 2.0 Slightly changed the way the ORM handles float fields to allow setting `digits=0` as a way to explicitly require a NUMERIC value but without enforcing/rounding the values at the ORM level, i.e. a truly full-precision field. NUMERIC type has unlimited precision but is less efficient so should not be used as the default behaviour, which is why we keep float8 as an alternative. Modified the view to display the product UOM factor with a 5 digits value by default. This value is for usability purpose only, the field still accepts bigger precision, by setting the `digits` option on the field in the form view. This change is safe in a stable series, the `digits=0` alternative is treated the same as the default `digits=None` everywhere in the framework, except when creating the database field. --- addons/product/product.py | 4 ++-- addons/product/product_view.xml | 4 ++-- openerp/osv/orm.py | 7 ++++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/addons/product/product.py b/addons/product/product.py index 7a36507aa79..ff0a6a770d7 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -132,10 +132,10 @@ class product_uom(osv.osv): 'name': fields.char('Unit of Measure', size=64, 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='Ratio', help='How many times this Unit of Measure is bigger than the reference Unit of Measure in this category:\n'\ diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 26bc294e436..7b4539e7dd3 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -421,8 +421,8 @@