[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:
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user