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 @@
+
+
+
+
+
+
+
+
+
+
+
+