From b2f0af4543293350365a43893f9184e0a8669b48 Mon Sep 17 00:00:00 2001 From: Joren Van Onder Date: Fri, 1 Dec 2017 13:52:31 -0800 Subject: [PATCH 1/6] [FIX] point_of_sale: allow searching a partner on >2 fields When searching for a partner the ' ' (SPC) character can be used as a field separator. You can search for e.g.: 'agr namur' which will return the 'Agrolait, 69 rue de Namur,...' partner. For some reason only the first space was considered a separator. So e.g.: 'agr namur belgium' wouldn't work. This commit makes it so that every space character is considered a separator. Fixes #21357 --- addons/point_of_sale/static/src/js/db.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index 4fdaa652a82..dd7cc59cb7d 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -321,7 +321,7 @@ var PosDB = core.Class.extend({ search_partner: function(query){ try { query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.'); - query = query.replace(' ','.+'); + query = query.replace(/ /g,'.+'); var re = RegExp("([0-9]+):.*?"+query,"gi"); }catch(e){ return []; From ed326e056946f5990357649245639a39b85134dc Mon Sep 17 00:00:00 2001 From: Ruter Date: Tue, 5 Dec 2017 12:23:25 +0800 Subject: [PATCH 2/6] [CLA] signature for ruter --- doc/cla/individual/ruter.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 doc/cla/individual/ruter.md diff --git a/doc/cla/individual/ruter.md b/doc/cla/individual/ruter.md new file mode 100644 index 00000000000..1560ba2b7f3 --- /dev/null +++ b/doc/cla/individual/ruter.md @@ -0,0 +1,11 @@ +China, 2017-12-05 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Ruter ruter.lv@gmail.com https://github.com/ruter From fe8668e509d36b48a6be0c82f46a44a0f553cbb6 Mon Sep 17 00:00:00 2001 From: Ruter Date: Tue, 5 Dec 2017 12:28:16 +0800 Subject: [PATCH 3/6] [FIX] web: Setting disable_favorites on Search View causes Exception --- addons/web/static/src/js/views/search_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/views/search_view.js b/addons/web/static/src/js/views/search_view.js index 65dbb4557e7..d11af68c107 100644 --- a/addons/web/static/src/js/views/search_view.js +++ b/addons/web/static/src/js/views/search_view.js @@ -337,7 +337,7 @@ var SearchView = View.extend({ }, set_default_filters: function () { var self = this, - default_custom_filter = this.$buttons && this.favorite_menu.get_default_filter(); + default_custom_filter = this.$buttons && this.favorite_menu && this.favorite_menu.get_default_filter(); if (!self.options.disable_custom_filters && default_custom_filter) { return this.favorite_menu.toggle_filter(default_custom_filter, true); } From 04bfd54013cd26d95332641789b4e2b294c4b3b5 Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Mon, 4 Dec 2017 16:59:07 +0100 Subject: [PATCH 4/6] [FIX] sales_team: multicompany issue typo 3ad1867e 3ad1867e could lead to wrong results in a multicompany setting because the change got the company from the lead but it was intended to be gotten from the user. Since ('company_id', 'child_of', False) returned "root" companies, the code still got a sales team but possibly one from another company if the user was on another company that its sales team. This would lead to an access issue that this commit solves. opw-784455 closes #21428 --- addons/sales_team/models/crm_team.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py index 2eebdd5eceb..ae491e7866e 100644 --- a/addons/sales_team/models/crm_team.py +++ b/addons/sales_team/models/crm_team.py @@ -19,7 +19,7 @@ class CrmTeam(models.Model): if 'default_team_id' in self.env.context: team_id = self.env['crm.team'].browse(self.env.context.get('default_team_id')) if not team_id or not team_id.exists(): - company_id = self.sudo(user_id).company_id.id + company_id = self.sudo(user_id).env.user.company_id.id team_id = self.env['crm.team'].sudo().search([ '|', ('user_id', '=', user_id), ('member_ids', '=', user_id), '|', ('company_id', '=', False), ('company_id', 'child_of', [company_id]) From ce038f5d1531107a1e45fd867de42e82940babdb Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Tue, 5 Dec 2017 13:29:06 +0100 Subject: [PATCH 5/6] [FIX] auth_oauth: correctly retrieve user facebook email Before this commit, you could never get the user's facebook email. Since there was no email field on facebook response, email would be then set to an auto generated value like 'provider_2_user_0123456789123456' by odoo. Now, we correctly request user's email to facebook. opw-782771 --- addons/auth_oauth/auth_oauth_data.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/auth_oauth/auth_oauth_data.xml b/addons/auth_oauth/auth_oauth_data.xml index 3b71b56be1f..7fc4633cc0a 100644 --- a/addons/auth_oauth/auth_oauth_data.xml +++ b/addons/auth_oauth/auth_oauth_data.xml @@ -16,7 +16,7 @@ https://www.facebook.com/dialog/oauth public_profile,email https://graph.facebook.com/me - + https://graph.facebook.com/me?fields=id,name,email fa fa-facebook-square Log in with facebook From 2a86e9245c91d6564257aad4ed69673576f10918 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Fri, 1 Dec 2017 11:35:03 +0100 Subject: [PATCH 6/6] [FIX][BACKPORT] Always round monetary values in database backport of afef71d6b92a2741ab56b4d039eba1624a45197f Original commit message: Let `create` and `write` round monetary field values before sending them to the database. Pass the values to be written to `field.convert_to_column`, so that the currency can be retrieved from the values, and the value be rounded. --- .../test_new_api/tests/test_new_fields.py | 68 +++++++++++++++---- odoo/fields.py | 55 ++++++++------- odoo/models.py | 10 +-- odoo/tools/float_utils.py | 18 ----- 4 files changed, 94 insertions(+), 57 deletions(-) diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index df8f7ba4f2d..e7579f5c909 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -5,7 +5,7 @@ from datetime import date, datetime from odoo.exceptions import AccessError, except_orm from odoo.tests import common -from odoo.tools import mute_logger +from odoo.tools import mute_logger, float_repr class TestFields(common.TransactionCase): @@ -305,21 +305,65 @@ class TestFields(common.TransactionCase): record.number = 2.4999999999999996 self.assertEqual(record.number, 2.50) + def check_monetary(self, record, amount, currency, msg=None): + # determine the possible roundings of amount + if currency: + ramount = currency.round(amount) + samount = float(float_repr(ramount, currency.decimal_places)) + else: + ramount = samount = amount + + # check the currency on record + self.assertEqual(record.currency_id, currency) + + # check the value on the record + self.assertIn(record.amount, [ramount, samount], msg) + + # check the value in the database + self.cr.execute('SELECT amount FROM test_new_api_mixed WHERE id=%s', [record.id]) + value = self.cr.fetchone()[0] + self.assertEqual(value, samount, msg) + 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) + model = self.env['test_new_api.mixed'] + currency = self.env['res.currency'].with_context(active_test=False) + amount = 14.70126 - # 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) + for rounding in [0.01, 0.0001, 1.0, 0]: + # first retrieve a currency corresponding to rounding + if rounding: + currency = currency.search([('rounding', '=', rounding)], limit=1) + self.assertTrue(currency, "No currency found for rounding %s" % rounding) + else: + # rounding=0 corresponds to currency=False + currency = currency.browse() - # 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) + # case 1: create with amount and currency + record = model.create({'amount': amount, 'currency_id': currency.id}) + self.check_monetary(record, amount, currency, 'create(amount, currency)') + + # case 2: assign amount + record.amount = 0 + record.amount = amount + self.check_monetary(record, amount, currency, 'assign(amount)') + + # case 3: write with amount and currency + record.write({'amount': 0, 'currency_id': False}) + record.write({'amount': amount, 'currency_id': currency.id}) + self.check_monetary(record, amount, currency, 'write(amount, currency)') + + # case 4: write with amount only + record.write({'amount': 0}) + record.write({'amount': amount}) + self.check_monetary(record, amount, currency, 'write(amount)') + + # case 5: write with amount on several records + records = record + model.create({'currency_id': currency.id}) + records.write({'amount': 0}) + records.write({'amount': amount}) + for record in records: + self.check_monetary(record, amount, currency, 'multi write(amount)') def test_21_date(self): """ test date fields """ diff --git a/odoo/fields.py b/odoo/fields.py index 60e3c83e6aa..349d74583c0 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -16,7 +16,7 @@ import xmlrpclib import psycopg2 from odoo.sql_db import LazyCursor -from odoo.tools import float_precision, float_repr, float_round, frozendict, \ +from odoo.tools import float_repr, float_round, frozendict, \ html_sanitize, human_size, pg_varchar, ustr, OrderedSet from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT @@ -811,7 +811,7 @@ class Field(object): """ Return the null value for this field in the record format. """ return False - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): """ Convert ``value`` from the ``write`` format to the SQL format. """ if value is None or value == False: return None @@ -1138,7 +1138,7 @@ class Boolean(Field): type = 'boolean' column_type = ('bool', 'bool') - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): return bool(value) def convert_to_cache(self, value, record, validate=True): @@ -1159,7 +1159,7 @@ class Integer(Field): _description_group_operator = property(attrgetter('group_operator')) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): return int(value or 0) def convert_to_cache(self, value, record, validate=True): @@ -1222,7 +1222,7 @@ class Float(Field): _description_digits = property(attrgetter('digits')) _description_group_operator = property(attrgetter('group_operator')) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): result = float(value or 0.0) digits = self.digits if digits: @@ -1275,25 +1275,34 @@ class Monetary(Field): assert self.currency_field in model._fields, \ "Field %s with unknown currency_field %r" % (self, self.currency_field) - def convert_to_column(self, value, record): - try: - return value.float_repr() # see float_precision.float_repr() - except Exception: - return float(value or 0.0) + def convert_to_column(self, value, record, values=None): + # retrieve currency from values or record + if values and self.currency_field in values: + field = record._fields[self.currency_field] + currency = field.convert_to_cache(values[self.currency_field], record) + currency = field.convert_to_record(currency, record) + else: + # Note: this is wrong if 'record' is several records with different + # currencies, which is functional nonsense and should not happen + currency = record[:1][self.currency_field] + + value = float(value or 0.0) + if currency: + return float_repr(currency.round(value), currency.decimal_places) + return value def convert_to_cache(self, value, record, validate=True): - if validate: - currency = record[self.currency_field] + # cache format: float + value = float(value or 0.0) + if validate and record[self.currency_field]: # FIXME @rco-odoo: currency may not be already initialized if it is # a function or related field! - if currency: - value = currency.round(float(value or 0.0)) - return float_precision(value, currency.decimal_places) - return float(value or 0.0) + value = record[self.currency_field].round(value) + return value def convert_to_read(self, value, record, use_name_get=True): # float_precision values are not supported in pure XMLRPC - return float(value) + return value def convert_to_write(self, value, record): return value @@ -1384,7 +1393,7 @@ class Char(_String): assert isinstance(self.size, (NoneType, int)), \ "Char field %s with non-integer size %r" % (self, self.size) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): #TODO: # * we need to remove the "value==False" from the next line BUT # for now too many things rely on this broken behavior @@ -1452,7 +1461,7 @@ class Html(_String): _description_strip_style = property(attrgetter('strip_style')) _description_strip_classes = property(attrgetter('strip_classes')) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): if value is None or value is False: return None if self.sanitize: @@ -1631,7 +1640,7 @@ class Binary(Field): _description_attachment = property(attrgetter('attachment')) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): # Binary values may be byte strings (python 2.6 byte array), but # the legacy OpenERP convention is to transfer and store binaries # as base64-encoded strings. The base64 string may be provided as a @@ -1791,7 +1800,7 @@ class Selection(Field): return item[1] return False - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): """ Convert ``value`` from the ``write`` format to the SQL format. """ if value is None or value is False: return None @@ -1931,7 +1940,7 @@ class Many2one(_Relational): """ records._cache[self] = self.convert_to_cache(value, records, validate=False) - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): return value or None def convert_to_cache(self, value, record, validate=True): @@ -2453,7 +2462,7 @@ class Serialized(Field): } column_type = ('text', 'text') - def convert_to_column(self, value, record): + def convert_to_column(self, value, record, values=None): return json.dumps(value) def convert_to_cache(self, value, record, validate=True): diff --git a/odoo/models.py b/odoo/models.py index 595250b54f2..387f4a1cfcb 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3584,7 +3584,7 @@ class BaseModel(object): if field.column_type: if single_lang or not (has_trans and field.translate is True): # val is not a translation: update the table - val = field.convert_to_column(val, self) + val = field.convert_to_column(val, self, vals) updates.append((name, field.column_format, val)) direct.append(name) else: @@ -3624,7 +3624,7 @@ class BaseModel(object): # Insert value to DB src_trans = vals[name] self.with_context(lang=None).write({name: src_trans}) - val = field.convert_to_column(vals[name], self) + val = field.convert_to_column(vals[name], self, vals) tname = "%s,%s" % (self._name, name) self.env['ir.translation']._set_ids( tname, 'model', self.env.lang, self.ids, val, src_trans) @@ -3848,7 +3848,7 @@ class BaseModel(object): parent_id = self.env[parent_model].create(parent_vals).id else: self.env[parent_model].browse(parent_id).write(parent_vals) - updates.append((self._inherits[parent_model], '%s', parent_id)) + vals[self._inherits[parent_model]] = parent_id # set boolean fields to False by default (to make search more powerful) for name, field in self._fields.iteritems(): @@ -3856,10 +3856,12 @@ class BaseModel(object): vals[name] = False # determine SQL values + self = self.browse() for name, val in vals.iteritems(): field = self._fields[name] if field.store and field.column_type: - updates.append((name, field.column_format, field.convert_to_column(val, self))) + column_val = field.convert_to_column(val, self, vals) + updates.append((name, field.column_format, column_val)) else: upd_todo.append(name) diff --git a/odoo/tools/float_utils.py b/odoo/tools/float_utils.py index 67777145dba..b5be74023bb 100644 --- a/odoo/tools/float_utils.py +++ b/odoo/tools/float_utils.py @@ -150,24 +150,6 @@ def float_repr(value, precision_digits): _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__": import time