From a17d30e5db53e507452f42a4bf6e72727664eeae Mon Sep 17 00:00:00 2001 From: Paul Stroobant Date: Thu, 18 Apr 2024 15:15:46 +0200 Subject: [PATCH] [FIX] stock: update uom ratio in stock.quant.reserved_quantity Steps to reproduce the issue: 1. Create a Storable Product and give it a UOM 2. Create an On Hand stock in a certain location 3. Go to Barcode and create a new Internal Transfer 4. Add the Product and with any quantity and click on Confirm (not Validate) 5. Edit the line of the Product (pencil icon), change the quantity and the UOM and click on Confirm 6. Validate the Transfer 7. Go back to the Product and click on the "On Hand" or the "Update Quantity" button 8. The reserved quantity is not null Explanation: When you change `stock.move.line.product_uom_id`, `stock.quant.reserved_quantity` (using `product.product.uom_id`) is not changed to reflect the new `uom.uom.factor`. You then have two routes: - In `stock_barcode`, `stock.move.line.product_uom_id` changes first, `stock.move.line.quantity` change is triggered through `stock.move.line._inverse_qty_done` afterwards. https://github.com/odoo/enterprise/blob/d04b69ba03877a9b4aae82fb061dca23b1bfc4bc/stock_barcode/models/stock_move_line.py#L58-L61 When calling `stock.move.line._synchronize_quant`, `stock.move.line.quantity_product_uom` will use the new `stock.move.line.product_uom_id` while `stock.quant.reserved_quantity` still reflects the old `uom.uom.factor`. https://github.com/odoo/odoo/blob/1b0dbb3645ad8b52c5260f1cbbc4f6bdee48461e/addons/stock/models/stock_move_line.py#L421-L422 (e.g.: going from `1 Dozens` to `2 Units` would give you `1.09 Dozens` in `stock.quant.reserved_quantity` instead of `0.17`) - There is a similar issue in _Inventory > Transfers > Internal_, where `stock.move.line.product_uom_id` changes at the same time instead. In that case, the whole operation will be done using the previous `stock.move.line.product_uom_id`, and changing `stock.move.line.product_uom_id` before changing `stock.move.line.quantity` would cause the same issue as in `stock_barcode`. (e.g.: going from `1 Dozens` to `2 Units` would give you `2 Dozens` in `stock.quant.reserved_quantity` instead of `0.17`) Suggested fix: There is a first check to make sure one of the values has changed, then each one will be assigned through a condition: - The first one will be `product_uom_id`, with which `uom.uom._compute_quantity` will be called. - The second condition will be `quantity`, which will be set in a `vals.get` in the `qty` parameter of the compute. opw-3798046 closes odoo/odoo#162380 X-original-commit: 2b8e073d082620ca3ac2342fa12c7f93a07335d1 Signed-off-by: Quentin Wolfs (quwo) Signed-off-by: Stroobant Paul (stpa) --- addons/stock/models/stock_move_line.py | 9 ++++---- addons/stock/tests/test_move.py | 29 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index ff5154ee22a..bb6d19d6d46 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -408,9 +408,10 @@ class StockMoveLine(models.Model): for ml in self: if ml.product_id.type != 'product' or ml.state == 'done': continue - if 'quantity' in vals: - new_reserved_qty = ml.product_uom_id._compute_quantity( - vals['quantity'], ml.product_id.uom_id, rounding_method='HALF-UP') + if 'quantity' in vals or 'product_uom_id' in vals: + new_ml_uom = updates.get('product_uom_id', ml.product_uom_id) + new_reserved_qty = new_ml_uom._compute_quantity( + vals.get('quantity', ml.quantity), ml.product_id.uom_id, rounding_method='HALF-UP') # Make sure `reserved_uom_qty` is not negative. if float_compare(new_reserved_qty, 0, precision_rounding=ml.product_id.uom_id.rounding) < 0: raise UserError(_('Reserving a negative quantity is not allowed.')) @@ -428,7 +429,7 @@ class StockMoveLine(models.Model): lot=updates.get('lot_id', ml.lot_id), package=updates.get('package_id', ml.package_id), owner=updates.get('owner_id', ml.owner_id)) - if 'quantity' in vals and vals['quantity'] != ml.quantity: + if ('quantity' in vals and vals['quantity'] != ml.quantity) or 'product_uom_id' in vals: moves_to_recompute_state |= ml.move_id # When editing a done move line, the reserved availability of a potential chained move is impacted. Take care of running again `_action_assign` on the concerned moves. diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index bef76702e97..0017e336cdd 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -6312,3 +6312,32 @@ class StockMove(TransactionCase): line_form.lot_ids.add(sn01) picking = picking_form.save() self.assertEqual(picking.move_ids_without_package.lot_ids, sn01) + + def test_change_move_line_uom(self): + """Check the reserved_quantity of the quant is correctly updated when changing the UOM in the move line""" + Quant = self.env['stock.quant'] + Quant._update_available_quantity(self.product, self.stock_location, 100) + quant = Quant._gather(self.product, self.stock_location) + move = self.env['stock.move'].create({ + 'name': 'Test move', + 'product_id': self.product.id, + 'product_uom_qty': 1, + 'product_uom': self.product.uom_id.id, + 'location_id': self.stock_location.id, + 'location_dest_id': self.customer_location.id, + }) + move._action_confirm() + move._action_assign() + ml = move.move_line_ids + + # The product's uom is in units, which means we currently have 1 reserved unit + self.assertEqual(quant.reserved_quantity, 1) + + # Firstly, we test changing the quantity and the uom together: 2 dozens = 24 reserved units + ml.write({'quantity': 2, 'product_uom_id': self.uom_dozen.id}) + self.assertEqual(quant.reserved_quantity, 24) + self.assertEqual(ml.quantity * self.uom_dozen.ratio, 24) + # Secondly, we test changing only the uom: 2 units -> expected 2 units + ml.write({'product_uom_id': self.uom_unit.id}) + self.assertEqual(quant.reserved_quantity, 2) + self.assertEqual(ml.quantity * self.uom_unit.ratio, 2)