diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 24e2c098eeb..8badb2b2fa4 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -1929,7 +1929,7 @@ class StockMove(models.Model): ('procure_method', '=', 'make_to_stock'), ('reservation_date', '<=', fields.Date.today())] moves_to_reserve = self.env['stock.move'].search(expression.AND([static_domain, expression.OR(domains)]), - order='reservation_date, priority desc, date asc') + order='reservation_date, priority desc, date asc, id asc') moves_to_reserve._action_assign() def _rollup_move_dests(self, seen): diff --git a/addons/stock/models/stock_rule.py b/addons/stock/models/stock_rule.py index f79b8588a67..70790294fcc 100644 --- a/addons/stock/models/stock_rule.py +++ b/addons/stock/models/stock_rule.py @@ -537,7 +537,7 @@ class ProcurementGroup(models.Model): # Search all confirmed stock_moves and try to assign them domain = self._get_moves_to_assign_domain(company_id) moves_to_assign = self.env['stock.move'].search(domain, limit=None, - order='reservation_date, priority desc, date asc') + order='reservation_date, priority desc, date asc, id asc') for moves_chunk in split_every(1000, moves_to_assign.ids): self.env['stock.move'].browse(moves_chunk).sudo()._action_assign() if use_new_cursor: diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index b4af1fabb7a..57b95a7ac48 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -2020,3 +2020,51 @@ class TestStockFlow(TestStockCommon): # Validates the two receipts => It should raise an error as there is duplicate SN. with self.assertRaises(ValidationError): (receipt_1 | receipt_2).button_validate() + + def test_assign_qty_to_first_move(self): + """ Suppose two out picking waiting for an available quantity. When receiving such + a quantity, the latter should be assign to the picking with the highest priority + and the earliest scheduled date. """ + def create_picking(picking_type, from_loc, to_loc, sequence=10, delay=0): + picking = self.PickingObj.create({ + 'picking_type_id': picking_type, + 'location_id': from_loc, + 'location_dest_id': to_loc, + }) + self.MoveObj.create({ + 'name': self.productA.name, + 'sequence': sequence, + 'date': fields.Datetime.add(fields.Datetime.now(), second=delay), + 'reservation_date': fields.Date.today(), + 'product_id': self.productA.id, + 'product_uom_qty': 1, + 'product_uom': self.productA.uom_id.id, + 'picking_id': picking.id, + 'location_id': from_loc, + 'location_dest_id': to_loc, + }) + picking.action_confirm() + return picking + + def validate_picking(picking): + res_dict = picking.button_validate() + wizard = Form(self.env[(res_dict.get('res_model'))].with_context(res_dict['context'])).save() + wizard.process() + + out01 = create_picking(self.picking_type_out, self.stock_location, self.customer_location) + out02 = create_picking(self.picking_type_out, self.stock_location, self.customer_location, sequence=2, delay=1) + in01 = create_picking(self.picking_type_in, self.supplier_location, self.stock_location, delay=2) + + validate_picking(in01) + self.assertEqual(out01.state, 'assigned') + self.assertEqual(out02.state, 'confirmed') + + validate_picking(out01) + + out03 = create_picking(self.picking_type_out, self.stock_location, self.customer_location, delay=3) + out03.priority = "1" + in02 = create_picking(self.picking_type_in, self.supplier_location, self.stock_location, delay=4) + + validate_picking(in02) + self.assertEqual(out02.state, 'confirmed') + self.assertEqual(out03.state, 'assigned')