diff --git a/addons/portal_stock/security/portal_security.xml b/addons/portal_stock/security/portal_security.xml
index 458de89b89b..2355a1331cf 100644
--- a/addons/portal_stock/security/portal_security.xml
+++ b/addons/portal_stock/security/portal_security.xml
@@ -6,7 +6,7 @@
Portal Follower Pickings
- ['|', ('message_partner_ids','in',[user.partner_id.id]), ('partner_id.id','=',user.partner_id.id)]
+ ['|', '|', ('message_partner_ids', 'in', [user.partner_id.id]), ('partner_id.id', '=', user.partner_id.id), ('sale_id.partner_id.id', '=', user.partner_id.id)]
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py
index 5c5e789f63a..f046bea5678 100644
--- a/addons/sale_stock/sale_stock.py
+++ b/addons/sale_stock/sale_stock.py
@@ -303,7 +303,14 @@ class StockPicking(models.Model):
break
picking.sale_id = sale_order.id if sale_order else False
- sale_id = fields.Many2one(comodel_name='sale.order', string="Sale Order", compute='_compute_sale_id')
+ def _search_sale_id(self, operator, value):
+ moves = self.env['stock.move'].search(
+ [('picking_id', '!=', False), ('procurement_id.sale_line_id.order_id', operator, value)]
+ )
+ return [('id', 'in', moves.mapped('picking_id').ids)]
+
+ sale_id = fields.Many2one(comodel_name='sale.order', string="Sale Order",
+ compute='_compute_sale_id', search='_search_sale_id')
class AccountInvoiceLine(models.Model):
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 7dd472d29b7..02b1405e8ae 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -1440,10 +1440,11 @@ class stock_picking(models.Model):
#Check moves with same product
product_qty = ops.qty_done if done_qtys else ops.product_qty
qty_to_assign = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, product_qty, ops.product_id.uom_id, context=context)
+ precision_rounding = ops.product_id.uom_id.rounding
for move_dict in prod2move_ids.get(ops.product_id.id, []):
move = move_dict['move']
for quant in move.reserved_quant_ids:
- if not qty_to_assign > 0:
+ if float_compare(qty_to_assign, 0, precision_rounding=precision_rounding) != 1:
break
if quant.id in quants_in_package_done:
continue
@@ -1466,7 +1467,7 @@ class stock_picking(models.Model):
qty_to_assign -= qty_on_link
lot_qty[quant.lot_id.id] -= qty_on_link
- qty_assign_cmp = float_compare(qty_to_assign, 0, precision_rounding=ops.product_id.uom_id.rounding)
+ qty_assign_cmp = float_compare(qty_to_assign, 0, precision_rounding=precision_rounding)
if qty_assign_cmp > 0:
#qty reserved is less than qty put in operations. We need to create a link but it's deferred after we processed
#all the quants (because they leave no choice on their related move and needs to be processed with higher priority)
diff --git a/addons/stock/views/report_stockpicking_operations.xml b/addons/stock/views/report_stockpicking_operations.xml
index 155524e5867..45e68fc7169 100644
--- a/addons/stock/views/report_stockpicking_operations.xml
+++ b/addons/stock/views/report_stockpicking_operations.xml
@@ -33,7 +33,7 @@
diff --git a/addons/web/static/src/js/views/form_relational_widgets.js b/addons/web/static/src/js/views/form_relational_widgets.js
index a1444bcae85..5b0987f0fc0 100644
--- a/addons/web/static/src/js/views/form_relational_widgets.js
+++ b/addons/web/static/src/js/views/form_relational_widgets.js
@@ -886,6 +886,9 @@ var FieldX2Many = AbstractManyField.extend({
}
return true;
},
+ is_false: function () {
+ return _(this.dataset.ids).isEmpty();
+ },
});
var X2ManyDataSet = data.BufferedDataSet.extend({
diff --git a/doc/cla/corporate/acsone.md b/doc/cla/corporate/acsone.md
index e1ab2495b1b..8e41f328f83 100644
--- a/doc/cla/corporate/acsone.md
+++ b/doc/cla/corporate/acsone.md
@@ -17,6 +17,7 @@ Anthony Muschang anthony.muschang@acsone.eu https://github.com/anthony-muschang
Cédric Pigeon cedric.pigeon@acsone.eu https://github.com/Cedric-Pigeon
Christelle De Coninck christelle.deconinck@acsone.eu https://github.com/chrisdec
Jonathan Nemry jonathan.nemry@acsone.eu https://github.com/JonathanNEMRY
+Denis Roussel denis.roussel@acsone.eu https://github.com/rousseldenis
Laetia Gangloff laetitia.gangloff@acsone.eu https://github.com/laetitia-gangloff
Laurent Mignon laurent.mignon@acsone.eu https://github.com/lmignon
Olivier Laurent olivier.laurent@acsone.eu https://github.com/Olivier-LAURENT
diff --git a/doc/cla/individual/engelant.md b/doc/cla/individual/engelant.md
new file mode 100644
index 00000000000..05a5d7b77fa
--- /dev/null
+++ b/doc/cla/individual/engelant.md
@@ -0,0 +1,9 @@
+Germany, 23 June 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,
+
+Anton Engelhardt mumie_die@yahoo.de https://github.com/engelant
diff --git a/openerp/addons/test_new_api/ir.model.access.csv b/openerp/addons/test_new_api/ir.model.access.csv
index 60cf53bcfa4..a8d921c30ae 100644
--- a/openerp/addons/test_new_api/ir.model.access.csv
+++ b/openerp/addons/test_new_api/ir.model.access.csv
@@ -11,3 +11,5 @@ access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1
access_test_function_noinfiniterecursion,access_test_function_noinfiniterecursion,model_test_old_api_function_noinfiniterecursion,,1,1,1,1
access_test_function_counter,access_test_function_counter,model_test_old_api_function_counter,,1,1,1,1
access_domain_bool,access_domain_bool,model_domain_bool,,1,1,1,1
+access_test_new_api_foo,access_test_new_api_foo,model_test_new_api_foo,,1,1,1,1
+access_test_new_api_bar,access_test_new_api_bar,model_test_new_api_bar,,1,1,1,1
diff --git a/openerp/addons/test_new_api/models.py b/openerp/addons/test_new_api/models.py
index 20b3a9f4bd3..adcf3baac64 100644
--- a/openerp/addons/test_new_api/models.py
+++ b/openerp/addons/test_new_api/models.py
@@ -284,3 +284,25 @@ class BoolModel(models.Model):
bool_true = fields.Boolean('b1', default=True)
bool_false = fields.Boolean('b2', default=False)
bool_undefined = fields.Boolean('b3')
+
+
+class Foo(models.Model):
+ _name = 'test_new_api.foo'
+
+ name = fields.Char()
+ value1 = fields.Integer()
+ value2 = fields.Integer()
+
+
+class Bar(models.Model):
+ _name = 'test_new_api.bar'
+
+ name = fields.Char()
+ foo = fields.Many2one('test_new_api.foo', compute='_compute_foo')
+ value1 = fields.Integer(related='foo.value1')
+ value2 = fields.Integer(related='foo.value2')
+
+ @api.depends('name')
+ def _compute_foo(self):
+ for bar in self:
+ bar.foo = self.env['test_new_api.foo'].search([('name', '=', bar.name)], limit=1)
diff --git a/openerp/addons/test_new_api/tests/test_new_fields.py b/openerp/addons/test_new_api/tests/test_new_fields.py
index 24706c658fb..63a164ab17f 100644
--- a/openerp/addons/test_new_api/tests/test_new_fields.py
+++ b/openerp/addons/test_new_api/tests/test_new_fields.py
@@ -361,6 +361,19 @@ class TestNewFields(common.TransactionCase):
discussion_field = discussion.fields_get(['name'])['name']
self.assertEqual(message_field['help'], discussion_field['help'])
+ def test_25_related_multi(self):
+ """ test write() on several related fields based on a common computed field. """
+ foo = self.env['test_new_api.foo'].create({'name': 'A', 'value1': 1, 'value2': 2})
+ bar = self.env['test_new_api.bar'].create({'name': 'A'})
+ self.assertEqual(bar.foo, foo)
+ self.assertEqual(bar.value1, 1)
+ self.assertEqual(bar.value2, 2)
+
+ foo.invalidate_cache()
+ bar.write({'value1': 3, 'value2': 4})
+ self.assertEqual(foo.value1, 3)
+ self.assertEqual(foo.value2, 4)
+
def test_26_inherited(self):
""" test inherited fields. """
# a bunch of fields are inherited from res_partner
diff --git a/openerp/models.py b/openerp/models.py
index de9158b5da8..648ff81dacd 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -3836,12 +3836,18 @@ class BaseModel(object):
if old_vals:
self._write(old_vals)
- # put the values of pure new-style fields into cache, and inverse them
if new_vals:
+ # put the values of pure new-style fields into cache
for record in self:
record._cache.update(record._convert_to_cache(new_vals, update=True))
+ # mark the fields as being computed, to avoid their invalidation
+ for key in new_vals:
+ self.env.computed[self._fields[key]].update(self._ids)
+ # inverse the fields
for key in new_vals:
self._fields[key].determine_inverse(self)
+ for key in new_vals:
+ self.env.computed[self._fields[key]].difference_update(self._ids)
return True
@@ -4150,10 +4156,16 @@ class BaseModel(object):
# create record with old-style fields
record = self.browse(self._create(old_vals))
- # put the values of pure new-style fields into cache, and inverse them
+ # put the values of pure new-style fields into cache
record._cache.update(record._convert_to_cache(new_vals))
+ # mark the fields as being computed, to avoid their invalidation
+ for key in new_vals:
+ self.env.computed[self._fields[key]].add(record.id)
+ # inverse the fields
for key in new_vals:
self._fields[key].determine_inverse(record)
+ for key in new_vals:
+ self.env.computed[self._fields[key]].discard(record.id)
return record