From df0a8595558b8d31f295253e07d419cbeb4b2eb1 Mon Sep 17 00:00:00 2001 From: "Tiffany Chang (tic)" Date: Fri, 3 Nov 2023 17:01:35 +0100 Subject: [PATCH] [FIX] stock: exclude not-picked mls at validation Requires barcode to be installed to reproduce (or customization of view/direct shell manipulation) Steps to reproduce: - create a SN tracked product with 2 serial numbers - setup a delivery with a demand of 1 of the SN tracked product - open the delivery in barcode - scan the product then scan the not reserved SN - validate Expected result: Only the scanned SN is marked as done Actual result: Both SNs are marked as done and the move quantity = 2 Issue is due to the code expecting all mls of a "picked" move to be processed as done. Typically when this flow is done in the backend, the user can see that they need to set the not used SN quantity to 0, but this is not the case in barcode. Therefore since barcode will mark the scanned SN ml as "picked", but not the other (reserved, but not scanned) SN, we check at time of picking validation whether or not all of the mls are marked as "picked" and if now, only count the ones that are marked. Note that same issue can occur when multi-location quants and non-reserved loc quantities are scanned. closes odoo/odoo#141210 Related: odoo/enterprise#50274 Signed-off-by: William Henrotin (whe) --- addons/stock/models/stock_move.py | 18 ++++++++++++++++++ addons/stock/models/stock_picking.py | 4 ++-- .../wizard/stock_backorder_confirmation.py | 5 +++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 557eeb37a95..ed62c36635b 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -1509,6 +1509,18 @@ Please change the quantity done or the rounding precision of your unit of measur location = forced_location or self.location_id return location.should_bypass_reservation() or self.product_id.type != 'product' + def _get_picked_quantity(self): + self.ensure_one() + if self.picked and any(not ml.picked for ml in self.move_line_ids): + picked_qty = 0 + for ml in self.move_line_ids: + if not ml.picked: + continue + picked_qty += ml.product_uom_id._compute_quantity(ml.quantity, self.product_uom, round=False) + return picked_qty + else: + return self.quantity + # necessary hook to be able to override move reservation to a restrict lot, owner, pack, location... def _get_available_quantity(self, location_id, lot_id=None, package_id=None, owner_id=None, strict=False, allow_negative=False): self.ensure_one() @@ -1796,10 +1808,16 @@ Please change the quantity done or the rounding precision of your unit of measur # Cancel moves where necessary ; we should do it before creating the extra moves because # this operation could trigger a merge of moves. + ml_ids_to_unlink = OrderedSet() for move in moves: + if move.picked: + # in theory, we should only have a mix of picked and non-picked mls in the barcode use case + # where non-scanned mls = not picked => we definitely don't want to validate them + ml_ids_to_unlink |= move.move_line_ids.filtered(lambda ml: not ml.picked).ids if (move.quantity <= 0 or not move.picked) and not move.is_inventory: if float_compare(move.product_uom_qty, 0.0, precision_rounding=move.product_uom.rounding) == 0 or cancel_backorder: move._action_cancel() + self.env['stock.move.line'].browse(ml_ids_to_unlink).unlink() # Create extra moves where necessary for move in moves: diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index 5a4fe949cfc..5ebbd475602 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -1220,8 +1220,8 @@ class Picking(models.Model): if picking.picking_type_id.create_backorder != 'ask': continue if any( - float_compare(move.quantity, move.product_uom_qty, precision_digits=prec) < 0 or - (move.product_uom_qty and not move.picked) + (move.product_uom_qty and not move.picked) or + float_compare(move._get_picked_quantity(), move.product_uom_qty, precision_digits=prec) < 0 for move in picking.move_ids if move.state != 'cancel' ): diff --git a/addons/stock/wizard/stock_backorder_confirmation.py b/addons/stock/wizard/stock_backorder_confirmation.py index 6b1cf7d6a80..bece171f154 100644 --- a/addons/stock/wizard/stock_backorder_confirmation.py +++ b/addons/stock/wizard/stock_backorder_confirmation.py @@ -41,10 +41,11 @@ class StockBackorderConfirmation(models.TransientModel): for pick_id in pickings: moves_to_log = {} for move in pick_id.move_ids: + picked_qty = move._get_picked_quantity() if float_compare(move.product_uom_qty, - move.quantity, + picked_qty, precision_rounding=move.product_uom.rounding) > 0: - moves_to_log[move] = (move.quantity, move.product_uom_qty) + moves_to_log[move] = (picked_qty, move.product_uom_qty) if moves_to_log: pick_id._log_less_quantities_than_expected(moves_to_log)