[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) <whe@odoo.com>
This commit is contained in:
Tiffany Chang (tic)
2023-11-07 19:32:30 +00:00
parent 351f5cf54e
commit df0a859555
3 changed files with 23 additions and 4 deletions
+18
View File
@@ -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:
+2 -2
View File
@@ -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'
):
@@ -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)