From 8fe8ca9948466064675beb3931b457b67ebdedc6 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 15 Dec 2016 00:57:16 +0100 Subject: [PATCH] [FIX] stock: picking assign for stock moves if batched in a one2many MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let’s say you have 10 units of products item1 and item2 in your stock. If you create a sale order with two order lines: 1 item1 and 1 item2, it’ll create one picking with two pack operations: 1 item1 and 1 item2. Validate the picking, everything goes fine. Now if you go back to the sale order and increase the quantity of the two order lines, let’s say you sold now 2 item1 and 2 item2, it’ll create two picking having each one a single pack operation: the first with a pack operation for 1 unit of item1, the second one with a pack operation of 1unit for item2. The expected result would be that a single new picking with the two pack operations is created. This bug is due to the fact that there’s a check on sale order line write that’ll call _action_procurement_create if you indeed increased the quantity of any line. This method will at some point call assign_picking on the model stock move. This method will try to find a non-done picking with the same procurement group as the one we’re working (the procurement group for our sale order). For the first write on sale.order.line, it won’t find one because there aren’t, so it’ll create one. For the second write on sale.order.line (so, for the second line) it should find the picking created by the first write, but it won’t and so it’ll create a second picking. The fact that it didn't find the picking create by the previous line is due to the fact that we do not update the computed fields between write on a one2many field since rev 7b8afc2 and that “group_id” on the model stock.picking is a computed field (a related). As we prefer to keep the behavior of not recomputing the computed fields between writes on a one2many (it’s a trade-off for performance reason, except in edge cases the recomputation is useless), we explicitly trigger the recomputation before looking for a candidate picking in the stock.move action_assign, so that the next searchs on picking.group_id will be succesful. This commit also introduces a test for this case. Closes #14718 --- addons/sale_stock/tests/test_sale_stock.py | 34 ++++++++++++++++++++++ addons/stock/models/stock_move.py | 8 +++++ 2 files changed, 42 insertions(+) diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py index e90130cb68c..ea2a5bb53dd 100644 --- a/addons/sale_stock/tests/test_sale_stock.py +++ b/addons/sale_stock/tests/test_sale_stock.py @@ -232,3 +232,37 @@ class TestSaleStock(TestSale): self.so.action_done() self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" when set to done') + + def test_04_create_picking_update_saleorderline(self): + """ + Test that updating multiple sale order lines after a succesful delivery creates a single picking containing + the new move lines. + """ + # sell two products + item1 = self.products['prod_order'] + item2 = self.products['prod_del'] + + self.so = self.env['sale.order'].create({ + 'partner_id': self.partner.id, + 'order_line': [ + (0, 0, {'name': item1.name, 'product_id': item1.id, 'product_uom_qty': 1, 'product_uom': item1.uom_id.id, 'price_unit': item1.list_price}), + (0, 0, {'name': item2.name, 'product_id': item2.id, 'product_uom_qty': 1, 'product_uom': item2.uom_id.id, 'price_unit': item2.list_price}), + ], + }) + self.so.action_confirm() + + # deliver them + self.assertEquals(len(self.so.picking_ids), 1) + self.so.picking_ids[0].action_done() + self.assertEquals(self.so.picking_ids[0].state, "done") + + # update the two original sale order lines + self.so.write({ + 'order_line': [ + (1, self.so.order_line[0].id, {'product_uom_qty': 2}), + (1, self.so.order_line[1].id, {'product_uom_qty': 2}), + ] + }) + + # a single picking should be created for the new delivery + self.assertEquals(len(self.so.picking_ids), 2) diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 6cc4a35a0bd..c2cfd811f3b 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -412,6 +412,14 @@ class StockMove(models.Model): type (moves should already have them identical). Otherwise, create a new picking to assign them to. """ Picking = self.env['stock.picking'] + + # If this method is called in batch by a write on a one2many and + # at some point had to create a picking, some next iterations could + # try to find back the created picking. As we look for it by searching + # on some computed fields, we have to force a recompute, else the + # record won't be found. + self.recompute() + for move in self: picking = Picking.search([ ('group_id', '=', move.group_id.id),