diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index c5375658518..bb0dd9366fc 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -865,11 +865,6 @@ class StockMove(models.Model): package_id=package_id, owner_id=owner_id, strict=strict ) except UserError: - # If it raises here, it means that the `available_quantity` brought by a done move line - # is not available on the quants itself. This could be the result of an inventory - # adjustment that removed totally of partially `available_quantity`. When this happens, we - # chose to do nothing. This situation could not happen on MTS move, because in this case - # `available_quantity` is directly the quantity on the quants themselves. taken_quantity = 0 # Find a candidate move line to update or create a new one. @@ -985,7 +980,17 @@ class StockMove(models.Model): available_move_lines[(move_line.location_id, move_line.lot_id, move_line.result_package_id, move_line.owner_id)] -= move_line.product_qty for (location_id, lot_id, package_id, owner_id), quantity in available_move_lines.items(): need = move.product_qty - sum(move.move_line_ids.mapped('product_qty')) - taken_quantity = move._update_reserved_quantity(need, quantity, location_id, lot_id, package_id, owner_id) + # `quantity` is what is brought by chained done move lines. We double check + # here this quantity is available on the quants themselves. If not, this + # could be the result of an inventory adjustment that removed totally of + # partially `quantity`. When this happens, we chose to reserve the maximum + # still available. This situation could not happen on MTS move, because in + # this case `quantity` is directly the quantity on the quants themselves. + available_quantity = self.env['stock.quant']._get_available_quantity( + move.product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True) + if float_is_zero(available_quantity, precision_rounding=move.product_id.uom_id.rounding): + continue + taken_quantity = move._update_reserved_quantity(need, min(quantity, available_quantity), location_id, lot_id, package_id, owner_id) if float_is_zero(taken_quantity, precision_rounding=move.product_id.uom_id.rounding): continue if need - taken_quantity == 0.0: diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 53948b477f9..8025cf4a13c 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -1751,7 +1751,7 @@ class StockMove(TransactionCase): ml.qty_done = 1 res_dict_for_back_order = picking_pack_cust.button_validate() backorder_wizard = self.env[(res_dict_for_back_order.get('res_model'))].browse(res_dict_for_back_order.get('res_id')) - backorder_wizard.with_context(debug=True).process() + backorder_wizard.process() backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_pack_cust.id)]) backordered_move = backorder.move_lines @@ -1782,6 +1782,57 @@ class StockMove(TransactionCase): self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.customer_location), 3) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.pack_location), 0) + def test_link_assign_10(self): + """ Test the assignment mechanism with partial availability. + """ + # make some stock: + # stock location: 2.0 + # pack location: -1.0 + self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2.0) + self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + + move_out = self.env['stock.move'].create({ + 'name': 'test_link_assign_out', + 'location_id': self.pack_location.id, + 'location_dest_id': self.customer_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + }) + move_out._action_confirm() + move_out._action_assign() + move_out.quantity_done = 1.0 + move_out._action_done() + self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.pack_location)), 1.0) + + move_stock_pack = self.env['stock.move'].create({ + 'name': 'test_link_assign_1_1', + 'location_id': self.stock_location.id, + 'location_dest_id': self.pack_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 2.0, + }) + move_pack_cust = self.env['stock.move'].create({ + 'name': 'test_link_assign_1_2', + 'location_id': self.pack_location.id, + 'location_dest_id': self.customer_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 2.0, + }) + move_stock_pack.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]}) + move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]}) + + (move_stock_pack + move_pack_cust)._action_confirm() + move_stock_pack._action_assign() + move_stock_pack.quantity_done = 2.0 + move_stock_pack._action_done() + self.assertEqual(len(move_pack_cust.move_line_ids), 1) + + self.assertAlmostEqual(move_pack_cust.reserved_availability, 1.0) + self.assertEqual(move_pack_cust.state, 'partially_available') + def test_use_unreserved_move_line_1(self): """ Test that validating a stock move linked to an untracked product reserved by another one correctly unreserves the other one.