diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index bc880da1585..7adb32e4102 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -695,6 +695,9 @@ class AccountInvoice(models.Model): line2[tmp]['credit'] = (am < 0) and -am or 0.0 line2[tmp]['amount_currency'] += l['amount_currency'] line2[tmp]['analytic_line_ids'] += l['analytic_line_ids'] + qty = l.get('quantity') + if qty: + line2[tmp]['quantity'] = line2[tmp].get('quantity', 0.0) + qty else: line2[tmp] = l line = [] diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index fa079ce364e..9597be2f660 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1091,8 +1091,11 @@ class AccountMoveLine(models.Model): } bank = self.env["account.bank.statement"].browse(vals.get('statement_id')) if bank.currency_id != bank.company_id.currency_id: + ctx = {} + if 'date' in vals: + ctx['date'] = vals['date'] temp['currency_id'] = bank.currency_id.id - temp['amount_currency'] = bank.company_id.currency_id.compute(tax_vals['amount'], bank.currency_id, round=True) + temp['amount_currency'] = bank.company_id.currency_id.with_context(ctx).compute(tax_vals['amount'], bank.currency_id, round=True) tax_lines_vals.append(temp) new_line = super(AccountMoveLine, self).create(vals) diff --git a/addons/point_of_sale/views/pos_order_view.xml b/addons/point_of_sale/views/pos_order_view.xml index 937a0436b8c..d462c73ed4e 100644 --- a/addons/point_of_sale/views/pos_order_view.xml +++ b/addons/point_of_sale/views/pos_order_view.xml @@ -41,7 +41,8 @@ - + + diff --git a/addons/product/product.py b/addons/product/product.py index d4980c769e6..638935e89c7 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -711,8 +711,9 @@ class product_template(osv.osv): def copy(self, cr, uid, id, default=None, context=None): if default is None: default = {} - template = self.browse(cr, uid, id, context=context) - default['name'] = _("%s (copy)") % (template['name']) + if 'name' not in default: + template = self.browse(cr, uid, id, context=context) + default['name'] = _("%s (copy)") % (template['name']) return super(product_template, self).copy(cr, uid, id, default=default, context=context) _defaults = { diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 95af0db21e1..bf94a196b9e 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -4107,6 +4107,7 @@ class stock_location_path(osv.osv): 'propagate': rule.propagate, 'push_rule_id': rule.id, 'warehouse_id': rule.warehouse_id and rule.warehouse_id.id or False, + 'procurement_id': False, } def _apply(self, cr, uid, rule, move, context=None): diff --git a/addons/web/doc/search_view.rst b/addons/web/doc/search_view.rst index 12b0eaf3104..76692a0d775 100644 --- a/addons/web/doc/search_view.rst +++ b/addons/web/doc/search_view.rst @@ -241,7 +241,7 @@ with directly by external objects or search view controls .. js:class:: openerp.web.search.Facet A `backbone model`_ representing a single facet of the current - research. May map to a search field, or to a more complex or + search. May map to a search field, or to a more complex or fuzzier input (e.g. a custom filter or an advanced search). .. js:attribute:: category diff --git a/addons/web/static/test/x2many.js b/addons/web/static/test/x2many.js index f57b03bcb1d..f45df41334e 100644 --- a/addons/web/static/test/x2many.js +++ b/addons/web/static/test/x2many.js @@ -69,7 +69,42 @@ Tour.register({ waitFor: '.tab-pane:last tr:has(.oe_list_field_cell):not(:has(.oe_list_record_selector)):contains(user_test)', }, + // add ourself as participant + { + title: "change tab to Participants", + element: '[data-toggle="tab"]:contains(Participants)' + }, + { + title: "click to add participants", + element: '.tab-pane:eq(2).active .oe_form_field_x2many_list_row_add a' + }, + { + title: "select Admin", + element: '.modal .oe_list_content tr:has(td[data-field="name"]:containsExact(Administrator)) .oe_list_record_selector input[type="checkbox"]' + }, + { + title: "save selected participants", + waitFor: '.modal .oe_list_content tr:has(td[data-field="name"]:containsExact(Administrator)) .oe_list_record_selector input[type="checkbox"]:propChecked', + element: '.o_selectcreatepopup_search_select' + }, + + // save + { + title: "save discussion", + waitFor: '.oe_form_field_many2many tbody tr td.oe_list_field_char:containsExact(Administrator)', + element: 'button.oe_form_button_save' + }, + // edit + { + title: "edit discussion", + element: 'button.oe_form_button_edit' + }, + // add message a + { + title: "Select First Tab", + element: 'a[role=tab]:first', + }, { title: "create new message a", element: '.oe_form_field_x2many_list_row_add:first a' @@ -167,15 +202,10 @@ Tour.register({ }, { title: "click to add participants", - element: '.tab-pane:eq(1).active .oe_form_field_x2many_list_row_add a' - }, - { - title: "select Admin", - element: '.modal .oe_list_content tr:has(td[data-field="name"]:containsExact(Administrator)) .oe_list_record_selector input[type="checkbox"]' + element: '.tab-pane:eq(2).active .oe_form_field_x2many_list_row_add a' }, { title: "select Demo User", - waitFor: '.modal .oe_list_content tr:has(td[data-field="name"]:containsExact(Administrator)) .oe_list_record_selector input[type="checkbox"]:propChecked', element: '.modal .oe_list_content tr:has(td[data-field="name"]:containsExact(Demo User)) .oe_list_record_selector input[type="checkbox"]' }, { @@ -214,7 +244,7 @@ Tour.register({ element: 'button.oe_form_button_edit' }, { - title: "change tab to Participants", + title: "change tab to Messages", waitFor: '.oe_form_editable', element: '[data-toggle="tab"]:contains(Messages)' }, diff --git a/doc/cla/corporate/Seekda.md b/doc/cla/corporate/Seekda.md new file mode 100644 index 00000000000..0b5c0addde0 --- /dev/null +++ b/doc/cla/corporate/Seekda.md @@ -0,0 +1,17 @@ +Austria, 2016-06-16 + +Seekda GmbH agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Holger Lausen holger.lausen@seekda.com https://github.com/holgerlausen + +List of contributors: + +Holger Lausen holger.lausen@seekda.com https://github.com/holgerlausen +Florian Kisser florian.kisser@seekda.com https://github.com/floriankisser + diff --git a/doc/cla/individual/williamjmorenor.md b/doc/cla/individual/williamjmorenor.md new file mode 100644 index 00000000000..b09599fe626 --- /dev/null +++ b/doc/cla/individual/williamjmorenor.md @@ -0,0 +1,9 @@ +Nicaragua, Jun 16th, 2016 + +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, + +William Moreno williamjmorenor@gmail.com https://github.com/williamjmorenor diff --git a/doc/reference/orm.rst b/doc/reference/orm.rst index 8b7b9c1a4ad..cf8718c6f1c 100644 --- a/doc/reference/orm.rst +++ b/doc/reference/orm.rst @@ -641,7 +641,7 @@ Model Reference .. automethod:: read .. automethod:: read_group - .. rubric:: Research + .. rubric:: Searching .. automethod:: search .. automethod:: search_count diff --git a/doc/reference/views.rst b/doc/reference/views.rst index 6bed888f571..bc2a34e8be6 100644 --- a/doc/reference/views.rst +++ b/doc/reference/views.rst @@ -822,7 +822,7 @@ attributes: ``default_group_by`` whether the kanban view should be grouped if no grouping is specified via - the action or the current research. Should be the name of the field to group + the action or the current search. Should be the name of the field to group by when no grouping is otherwise specified ``default_order`` cards sorting order used if the user has not already sorted the records (via diff --git a/openerp/addons/base/ir/ir_model_view.xml b/openerp/addons/base/ir/ir_model_view.xml index 17c9ead1f37..8b729d6d6df 100644 --- a/openerp/addons/base/ir/ir_model_view.xml +++ b/openerp/addons/base/ir/ir_model_view.xml @@ -106,7 +106,7 @@ diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index cdd059ca719..b90598ebeb4 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -439,9 +439,8 @@ class QWeb(orm.AbstractModel): d = qwebcontext.copy() if 'lang' in template_attributes: - init_lang = d.context.get('lang', 'en_US') lang = template_attributes['lang'] - d.context['lang'] = self.eval(lang, d) or lang + d.context = dict(d.context, lang=self.eval(lang, d) or lang) if not self.pool['res.lang'].search(d.cr, d.uid, [('code', '=', d.context['lang'])], count=True, context=d.context): d.context['lang'] = qwebcontext.get('res_company') and qwebcontext.get('res_company').partner_id.lang or 'en_US' _logger.info("'%s' is not a valid language code, is an empty field or is not installed, falling back to %s" % (lang, d.context['lang'])) @@ -459,10 +458,6 @@ class QWeb(orm.AbstractModel): d['generated_attributes'] = generated_attributes res = self.render(cr, uid, template, d) - # we need to reset the lang after the rendering - if 'lang' in template_attributes: - d.context['lang'] = init_lang - return res def render_tag_call_assets(self, element, template_attributes, generated_attributes, qwebcontext): diff --git a/openerp/addons/test_new_api/ir.model.access.csv b/openerp/addons/test_new_api/ir.model.access.csv index fe025c26751..60cf53bcfa4 100644 --- a/openerp/addons/test_new_api/ir.model.access.csv +++ b/openerp/addons/test_new_api/ir.model.access.csv @@ -4,6 +4,7 @@ access_test_new_api_bravo,access_test_new_api_bravo,model_test_new_api_bravo,,1, access_category,test_new_api_category,test_new_api.model_test_new_api_category,,1,1,1,1 access_discussion,test_new_api_discussion,test_new_api.model_test_new_api_discussion,,1,1,1,1 access_message,test_new_api_message,test_new_api.model_test_new_api_message,,1,1,1,1 +access_emailmessage,test_new_api_emailmessage,test_new_api.model_test_new_api_emailmessage,,1,1,1,1 access_multi,test_new_api_multi,test_new_api.model_test_new_api_multi,,1,1,1,1 access_multi_line,test_new_api_multi_line,test_new_api.model_test_new_api_multi_line,,1,1,1,1 access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1 diff --git a/openerp/addons/test_new_api/models.py b/openerp/addons/test_new_api/models.py index e1760f572bc..cb32d45e1dc 100644 --- a/openerp/addons/test_new_api/models.py +++ b/openerp/addons/test_new_api/models.py @@ -130,6 +130,9 @@ class Discussion(models.Model): message_concat = fields.Text(string='Message concatenate') important_messages = fields.One2many('test_new_api.message', 'discussion', domain=[('important', '=', True)]) + emails = fields.One2many('test_new_api.emailmessage', 'discussion') + important_emails = fields.One2many('test_new_api.emailmessage', 'discussion', + domain=[('important', '=', True)]) @api.onchange('moderator') def _onchange_moderator(self): @@ -210,6 +213,14 @@ class Message(models.Model): return [('author.partner_id', operator, value)] +class EmailMessage(models.Model): + _name = 'test_new_api.emailmessage' + _inherits = {'test_new_api.message': 'message'} + + message = fields.Many2one('test_new_api.message', 'Message', + required=True, ondelete='cascade') + email_to = fields.Char('To') + class Multi(models.Model): """ Model for testing multiple onchange methods in cascade that modify a one2many field several times. diff --git a/openerp/addons/test_new_api/tests/test_onchange.py b/openerp/addons/test_new_api/tests/test_onchange.py index b2c53115c20..adecda3e4aa 100644 --- a/openerp/addons/test_new_api/tests/test_onchange.py +++ b/openerp/addons/test_new_api/tests/test_onchange.py @@ -12,6 +12,7 @@ class TestOnChange(common.TransactionCase): super(TestOnChange, self).setUp() self.Discussion = self.env['test_new_api.discussion'] self.Message = self.env['test_new_api.message'] + self.EmailMessage = self.env['test_new_api.emailmessage'] def test_default_get(self): """ checking values returned by default_get() """ @@ -88,7 +89,7 @@ class TestOnChange(common.TransactionCase): self.assertEqual(field_onchange.get('messages'), '1') self.assertItemsEqual( strip_prefix('messages.', field_onchange), - ['author', 'body', 'name', 'size'], + ['author', 'body', 'name', 'size', 'important'], ) # modify discussion name @@ -104,6 +105,7 @@ class TestOnChange(common.TransactionCase): 'body': BODY, 'author': USER.id, 'size': len(BODY), + 'important': False, }), ], } @@ -117,12 +119,14 @@ class TestOnChange(common.TransactionCase): 'body': message.body, 'author': message.author.name_get()[0], 'size': message.size, + 'important': message.important, }), (0, 0, { 'name': "[%s] %s" % ("Foo", USER.name), 'body': BODY, 'author': USER.name_get()[0], 'size': len(BODY), + 'important': False, }), ]) @@ -223,3 +227,71 @@ class TestOnChange(common.TransactionCase): self.assertIn('message_concat', result['value']) self.assertEqual(result['value']['message_concat'], "\n".join(["%s:%s" % (m.name, m.body) for m in discussion.messages])) + + def test_onchange_one2many_with_domain_on_related_field(self): + """ test the value of the one2many field when defined with a domain on a related field""" + discussion = self.env.ref('test_new_api.discussion_0') + demo = self.env.ref('base.user_demo') + + # mimic UI behaviour, so we get subfields + # (we need at least subfield: 'important_emails.important') + view_info = self.Discussion.fields_view_get( + view_id=self.env.ref('test_new_api.discussion_form').id, + view_type='form') + field_onchange = self.Discussion._onchange_spec(view_info=view_info) + self.assertEqual(field_onchange.get('messages'), '1') + + BODY = "What a beautiful day!" + USER = self.env.user + + # create standalone email + email = self.EmailMessage.create({ + 'discussion': discussion.id, + 'name': "[%s] %s" % ('', USER.name), + 'body': BODY, + 'author': USER.id, + 'important': False, + 'email_to': demo.email, + }) + + # check if server-side cache is working correctly + self.env.invalidate_all() + self.assertIn(email, discussion.emails) + self.assertNotIn(email, discussion.important_emails) + email.important = True + self.assertIn(email, discussion.important_emails) + + # check that when trigger an onchange, we don't reset important emails + # (force `invalidate_all` as but appear in onchange only when we get a + # cache miss) + self.env.invalidate_all() + self.assertEqual(len(discussion.messages), 4) + values = { + 'name': "Foo Bar", + 'moderator': demo.id, + 'categories': [(4, cat.id) for cat in discussion.categories], + 'messages': [(4, msg.id) for msg in discussion.messages], + 'participants': [(4, usr.id) for usr in discussion.participants], + 'message_changes': 0, + 'important_messages': [(4, msg.id) for msg in discussion.important_messages], + 'important_emails': [(4, eml.id) for eml in discussion.important_emails], + } + result = discussion.onchange(values, 'name', field_onchange) + + # When one2many domain contains non-computed field, things are ok + self.assertEqual(result['value']['important_messages'], + [(5,)] + [(4, msg.id) for msg in discussion.important_messages]) + + # But here with commit 5676d81, we get value of: [(2, email.id)] + self.assertEqual( + result['value']['important_emails'], + [(5,), + (1, email.id, { + 'name': u'[Foo Bar] %s' % USER.name, + 'body': email.body, + 'author': USER.name_get()[0], + 'important': True, + 'email_to': demo.email, + 'size': email.size, + })] + ) diff --git a/openerp/addons/test_new_api/views.xml b/openerp/addons/test_new_api/views.xml index f9d84d38494..9cc5c6ce2ce 100644 --- a/openerp/addons/test_new_api/views.xml +++ b/openerp/addons/test_new_api/views.xml @@ -59,12 +59,35 @@ +
+ + +