[FIX] stock: define the order of products allocation

The allocation order is not based on the pickings order

To reproduce the issue:
(Need sale_management,mrp)
1. Create a storable product P
2. Create a BoM for another product dummy_P and add 1xP as component
3. Create and confirm a SO with 1x product P
4. Create and confirm a MO for 1x dummy_P
5. Process a receipt transfer with one P

Error: On the SO, the reserved quantity is still 0. The product has been
reserved for the MO, even if the picking of the SO has been created
first

The default order of several SM is based on the sequence and the
identifier:
https://github.com/odoo/odoo/blob/abc9fdaae2927214d98082f391a1dc0fc75e4c77/addons/stock/models/stock_move.py#L26

The default value of the field `sequence` is 10:
https://github.com/odoo/odoo/blob/abc9fdaae2927214d98082f391a1dc0fc75e4c77/addons/stock/models/stock_move.py#L26

When creating a SO, the field isn't defined so its default value is used
(10). However, when creating the MO, the field is defined thanks to the
sequence of the related BOM line:
https://github.com/odoo/odoo/blob/864d90a064f093bd6ba24d8464ee491a443a320e/addons/mrp/models/mrp_production.py#L940
Which, in our case, is equal to 1

As a result, when allocating the quantities in `_action_assign`, the
recordset will contain first the SM of the MO and then the SM of the SO.

This commit ensures the order will be based on the priority, the date
and the identifier (the latter allows the search result to be
deterministic)

Side note: this commit also adds the identifier to the search in
`_run_scheduler_tasks` to keep consistency between the different
flows

OPW-2524205

closes odoo/odoo#78497

X-original-commit: da2b336d467d95cda8ed21707535fb7119f5d777
Signed-off-by: Rémy Voet <ryv-odoo@users.noreply.github.com>
Signed-off-by: Adrien Widart <adwid@users.noreply.github.com>
This commit is contained in:
Adrien Widart
2021-10-18 15:12:55 +00:00
parent 2234f26a28
commit 60e7803f3f
3 changed files with 50 additions and 2 deletions
+1 -1
View File
@@ -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):
+1 -1
View File
@@ -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:
+48
View File
@@ -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')