diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 744e7fe01bd..1b64256df4f 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -5,6 +5,7 @@ from collections import Counter from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError +from odoo.tools import OrderedSet from odoo.tools.float_utils import float_compare, float_is_zero, float_round from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG @@ -431,9 +432,9 @@ class StockMoveLine(models.Model): # adjustment, enforce some rules on the `lot_id` field. If `qty_done` is null, we unlink # the line. It is mandatory in order to free the reservation and correctly apply # `action_done` on the next move lines. - ml_to_delete = self.env['stock.move.line'] - ml_to_create_lot = self.env['stock.move.line'] - tracked_ml_without_lot = self.env['stock.move.line'] + ml_ids_tracked_without_lot = OrderedSet() + ml_ids_to_delete = OrderedSet() + ml_ids_to_create_lot = OrderedSet() for ml in self: # Check here if `ml.qty_done` respects the rounding of `ml.product_uom_id`. uom_qty = float_round(ml.qty_done, precision_rounding=ml.product_uom_id.rounding, rounding_method='HALF-UP') @@ -458,11 +459,11 @@ class StockMoveLine(models.Model): ('company_id', '=', ml.company_id.id), ('product_id', '=', ml.product_id.id), ('name', '=', ml.lot_name), - ]) + ], limit=1) if lot: ml.lot_id = lot.id else: - ml_to_create_lot |= ml + ml_ids_to_create_lot.add(ml.id) elif not picking_type_id.use_create_lots and not picking_type_id.use_existing_lots: # If the user disabled both `use_create_lots` and `use_existing_lots` # checkboxes on the picking type, he's allowed to enter tracked @@ -473,25 +474,29 @@ class StockMoveLine(models.Model): # tracked products without a `lot_id`. continue - if not ml.lot_id and ml not in ml_to_create_lot: - tracked_ml_without_lot |= ml + if not ml.lot_id and ml.id not in ml_ids_to_create_lot: + ml_ids_tracked_without_lot.add(ml.id) elif qty_done_float_compared < 0: raise UserError(_('No negative quantities allowed')) else: - ml_to_delete |= ml + ml_ids_to_delete.add(ml.id) - if tracked_ml_without_lot: + if ml_ids_tracked_without_lot: + mls_tracked_without_lot = self.env['stock.move.line'].browse(ml_ids_tracked_without_lot) raise UserError(_('You need to supply a Lot/Serial Number for product: \n - ') + - '\n - '.join(tracked_ml_without_lot.mapped('product_id.display_name'))) + '\n - '.join(mls_tracked_without_lot.mapped('product_id.display_name'))) + ml_to_create_lot = self.env['stock.move.line'].browse(ml_ids_to_create_lot) ml_to_create_lot._create_and_assign_production_lot() - ml_to_delete.unlink() + mls_to_delete = self.env['stock.move.line'].browse(ml_ids_to_delete) + mls_to_delete.unlink() - (self - ml_to_delete)._check_company() + mls_todo = (self - mls_to_delete) + mls_todo._check_company() # Now, we can actually move the quant. - done_ml = self.env['stock.move.line'] - for ml in self - ml_to_delete: + ml_ids_to_ignore = OrderedSet() + for ml in mls_todo: if ml.product_id.type == 'product': rounding = ml.product_uom_id.rounding @@ -499,7 +504,8 @@ class StockMoveLine(models.Model): if not ml._should_bypass_reservation(ml.location_id) and float_compare(ml.qty_done, ml.product_uom_qty, precision_rounding=rounding) > 0: qty_done_product_uom = ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id, rounding_method='HALF-UP') extra_qty = qty_done_product_uom - ml.product_qty - ml._free_reservation(ml.product_id, ml.location_id, extra_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, ml_to_ignore=done_ml) + ml_to_ignore = self.env['stock.move.line'].browse(ml_ids_to_ignore) + ml._free_reservation(ml.product_id, ml.location_id, extra_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, ml_to_ignore=ml_to_ignore) # unreserve what's been reserved if not ml._should_bypass_reservation(ml.location_id) and ml.product_id.type == 'product' and ml.product_qty: try: @@ -518,9 +524,9 @@ class StockMoveLine(models.Model): Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id) Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id) Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date) - done_ml |= ml + ml_ids_to_ignore.add(ml.id) # Reset the reserved quantity as we just moved it to the destination location. - (self - ml_to_delete).with_context(bypass_reservation_update=True).write({ + mls_todo.with_context(bypass_reservation_update=True).write({ 'product_uom_qty': 0.00, 'date': fields.Datetime.now(), })