[FIX] stock: unreserve the correct quantity
case: - create a UoM 'Hundreds' related to UoM 'Units', bigger by factor x100 - create a delivery picking - create a move with the UoM Hundreds, indicate 10 Hundreds - validate, 1000 should be reserved - unreserve, only 10 units are unreserved, 990 are still reserved This is because the stock move line takes the UoM of the stock move by default on the `quantity` field and `unreserve` uses that field instead of `quantity_product_uom` This bug led to the discovery of other cases where `quantity_product_uom` should be used instead of `quantity` closes odoo/odoo#144574 Related: odoo/enterprise#51964 Signed-off-by: Arnold Moyaux (arm) <arm@odoo.com>
This commit is contained in:
@@ -273,7 +273,7 @@ class TestMrpProductionBackorder(TestMrpCommon):
|
||||
p2_bo_mls = production.procurement_group_id.mrp_production_ids[-1].move_raw_ids.filtered(lambda m: m.product_id == p2).move_line_ids
|
||||
self.assertEqual(len(p2_bo_mls), 1)
|
||||
self.assertEqual(p2_bo_mls.lot_id, lot1)
|
||||
self.assertEqual(p2_bo_mls.quantity, 2)
|
||||
self.assertEqual(p2_bo_mls.quantity_product_uom, 2)
|
||||
|
||||
def test_uom_backorder(self):
|
||||
"""
|
||||
|
||||
@@ -412,7 +412,7 @@ class TestPurchaseOrder(ValuationReconciliationTestCommon):
|
||||
self.assertEqual(purchase_order.state, 'purchase')
|
||||
self.assertEqual(len(purchase_order.picking_ids), 1)
|
||||
self.assertEqual(len(purchase_order.picking_ids.move_line_ids), 1)
|
||||
self.assertEqual(purchase_order.picking_ids.move_line_ids.quantity, 7)
|
||||
self.assertEqual(purchase_order.picking_ids.move_line_ids.quantity_product_uom, 7)
|
||||
|
||||
|
||||
purchase_order.order_line.product_qty = 4
|
||||
@@ -421,7 +421,7 @@ class TestPurchaseOrder(ValuationReconciliationTestCommon):
|
||||
purchase_order.button_confirm()
|
||||
self.assertEqual(len(purchase_order.picking_ids), 1)
|
||||
self.assertEqual(len(purchase_order.picking_ids.move_line_ids), 1)
|
||||
self.assertEqual(purchase_order.picking_ids.move_line_ids.quantity, 4)
|
||||
self.assertEqual(purchase_order.picking_ids.move_line_ids.quantity_product_uom, 4)
|
||||
|
||||
def test_message_qty_already_received(self):
|
||||
self.env.user.write({'company_id': self.company_data['company'].id})
|
||||
|
||||
@@ -259,7 +259,7 @@ class Repair(models.Model):
|
||||
@api.depends('move_ids', 'state', 'move_ids.product_uom_qty')
|
||||
def _compute_unreserve_visible(self):
|
||||
for repair in self:
|
||||
already_reserved = repair.state not in ('done', 'cancel') and any(repair.mapped('move_ids.move_line_ids.quantity'))
|
||||
already_reserved = repair.state not in ('done', 'cancel') and any(repair.mapped('move_ids.move_line_ids.quantity_product_uom'))
|
||||
|
||||
repair.unreserve_visible = already_reserved
|
||||
repair.reserve_visible = (
|
||||
|
||||
@@ -207,7 +207,7 @@ class Location(models.Model):
|
||||
for f in {'usage', 'scrap_location'}))
|
||||
reserved_quantities = self.env['stock.move.line'].search_count([
|
||||
('location_id', 'in', modified_locations.ids),
|
||||
('quantity', '>', 0),
|
||||
('quantity_product_uom', '>', 0),
|
||||
])
|
||||
if reserved_quantities:
|
||||
raise UserError(_(
|
||||
|
||||
@@ -1565,7 +1565,7 @@ Please change the quantity done or the rounding precision of your unit of measur
|
||||
quantity += ml.product_uom_id._compute_quantity(ml.quantity, ml.product_id.uom_id)
|
||||
grouped_move_lines_out[k] = quantity
|
||||
for k, g in groupby(move_lines_out_reserved, key=_keys_out_groupby):
|
||||
grouped_move_lines_out[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('quantity'))
|
||||
grouped_move_lines_out[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('quantity_product_uom'))
|
||||
|
||||
return grouped_move_lines_out
|
||||
|
||||
@@ -1683,11 +1683,11 @@ Please change the quantity done or the rounding precision of your unit of measur
|
||||
available_move_lines = move._get_available_move_lines(assigned_moves_ids, partially_available_moves_ids)
|
||||
if not available_move_lines:
|
||||
continue
|
||||
for move_line in move.move_line_ids.filtered(lambda m: m.quantity):
|
||||
for move_line in move.move_line_ids.filtered(lambda m: m.quantity_product_uom):
|
||||
if available_move_lines.get((move_line.location_id, move_line.lot_id, move_line.result_package_id, move_line.owner_id)):
|
||||
available_move_lines[(move_line.location_id, move_line.lot_id, move_line.result_package_id, move_line.owner_id)] -= move_line.quantity
|
||||
available_move_lines[(move_line.location_id, move_line.lot_id, move_line.result_package_id, move_line.owner_id)] -= move_line.quantity_product_uom
|
||||
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('quantity'))
|
||||
need = move.product_qty - sum(move.move_line_ids.mapped('quantity_product_uom'))
|
||||
# `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
|
||||
|
||||
@@ -290,7 +290,7 @@ class StockMoveLine(models.Model):
|
||||
ON
|
||||
stock_move_line (id, company_id, product_id, lot_id, location_id, owner_id, package_id)
|
||||
WHERE
|
||||
(state IS NULL OR state NOT IN ('cancel', 'done')) AND quantity > 0 AND not picked""")
|
||||
(state IS NULL OR state NOT IN ('cancel', 'done')) AND quantity_product_uom > 0 AND not picked""")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
@@ -484,9 +484,9 @@ class StockMoveLine(models.Model):
|
||||
quants_by_product = self.env['stock.quant']._get_quants_by_products_locations(self.product_id, self.location_id)
|
||||
for ml in self:
|
||||
# Unlinking a move line should unreserve.
|
||||
if not float_is_zero(ml.quantity, precision_digits=precision) and ml.move_id and not ml.move_id._should_bypass_reservation(ml.location_id):
|
||||
if not float_is_zero(ml.quantity_product_uom, precision_digits=precision) and ml.move_id and not ml.move_id._should_bypass_reservation(ml.location_id):
|
||||
quants = quants_by_product[ml.product_id.id]
|
||||
quants._update_reserved_quantity(ml.product_id, ml.location_id, -ml.quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
quants._update_reserved_quantity(ml.product_id, ml.location_id, -ml.quantity_product_uom, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
moves = self.mapped('move_id')
|
||||
res = super().unlink()
|
||||
if moves:
|
||||
@@ -705,7 +705,7 @@ class StockMoveLine(models.Model):
|
||||
('location_id', '=', location_id.id),
|
||||
('owner_id', '=', owner_id.id if owner_id else False),
|
||||
('package_id', '=', package_id.id if package_id else False),
|
||||
('quantity', '>', 0.0),
|
||||
('quantity_product_uom', '>', 0.0),
|
||||
('picked', '=', False),
|
||||
('id', 'not in', tuple(ml_ids_to_ignore)),
|
||||
]
|
||||
|
||||
@@ -2062,12 +2062,12 @@ class StockMove(TransactionCase):
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.supplier_location.id,
|
||||
'product_id': self.product.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 100.0,
|
||||
'product_uom': self.uom_dozen.id,
|
||||
'product_uom_qty': 10.0,
|
||||
})
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 150.0)
|
||||
self.assertEqual(move1.availability, 100.0)
|
||||
self.assertEqual(move1.availability, 120.0)
|
||||
|
||||
# confirmation
|
||||
move1._action_confirm()
|
||||
@@ -2077,7 +2077,7 @@ class StockMove(TransactionCase):
|
||||
move1._action_assign()
|
||||
self.assertEqual(move1.state, 'assigned')
|
||||
self.assertEqual(len(move1.move_line_ids), 1)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 50.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 30.0)
|
||||
|
||||
# unreserve
|
||||
move1._do_unreserve()
|
||||
|
||||
@@ -2622,7 +2622,7 @@ class TestStockUOM(TestStockCommon):
|
||||
('lot_id', '=', quant_LtDA.lot_id.id),
|
||||
('package_id', '=', quant_LtDA.package_id.id),
|
||||
('owner_id', '=', quant_LtDA.owner_id.id),
|
||||
('quantity', '!=', 0)
|
||||
('quantity_product_uom', '!=', 0)
|
||||
])
|
||||
reserved_on_move_lines_LtDA = sum(move_lines_LtDA.mapped('quantity_product_uom'))
|
||||
|
||||
@@ -2632,7 +2632,7 @@ class TestStockUOM(TestStockCommon):
|
||||
('lot_id', '=', quant_GtDA.lot_id.id),
|
||||
('package_id', '=', quant_GtDA.package_id.id),
|
||||
('owner_id', '=', quant_GtDA.owner_id.id),
|
||||
('quantity', '!=', 0)
|
||||
('quantity_product_uom', '!=', 0)
|
||||
])
|
||||
reserved_on_move_lines_GtDA = sum(move_lines_GtDA.mapped('quantity_product_uom'))
|
||||
|
||||
|
||||
@@ -1152,9 +1152,9 @@ class StockQuantRemovalStrategy(TransactionCase):
|
||||
self.assertEqual(len(move.move_line_ids), 12)
|
||||
self.assertRecordValues(
|
||||
move.move_line_ids,
|
||||
[{'quantity': 1000}] +
|
||||
[{'quantity': 50}] * 5 +
|
||||
[{'quantity': 5}] * 6
|
||||
[{'quantity_product_uom': 1000}] +
|
||||
[{'quantity_product_uom': 50}] * 5 +
|
||||
[{'quantity_product_uom': 5}] * 6
|
||||
)
|
||||
|
||||
def test_least_package_removal_strategy_not_possible(self):
|
||||
@@ -1183,7 +1183,7 @@ class StockQuantRemovalStrategy(TransactionCase):
|
||||
self.assertEqual(len(move.move_line_ids), 2)
|
||||
self.assertRecordValues(
|
||||
move.move_line_ids,
|
||||
[{'quantity': 10}] + [{'quantity': 3}]
|
||||
[{'quantity_product_uom': 10}] + [{'quantity_product_uom': 3}]
|
||||
)
|
||||
# Make sure it selects the smallest possible package as best leaf.
|
||||
self.assertEqual(
|
||||
@@ -1217,9 +1217,9 @@ class StockQuantRemovalStrategy(TransactionCase):
|
||||
self.assertEqual(len(move.move_line_ids), 8)
|
||||
self.assertRecordValues(
|
||||
move.move_line_ids,
|
||||
[{'quantity': 2}] +
|
||||
[{'quantity': 10}] * 5 +
|
||||
[{'quantity': 5}] * 2
|
||||
[{'quantity_product_uom': 2}] +
|
||||
[{'quantity_product_uom': 10}] * 5 +
|
||||
[{'quantity_product_uom': 5}] * 2
|
||||
)
|
||||
|
||||
def test_clean_quant_after_package_move(self):
|
||||
|
||||
@@ -2410,7 +2410,7 @@ class TestStockFlow(TestStockCommon):
|
||||
in_stock_picking = in_stock_move.picking_id
|
||||
in_stock_picking.button_validate()
|
||||
|
||||
self.assertEqual(out_move.move_line_ids.quantity, 1.0, 'The out move should be reserved')
|
||||
self.assertEqual(out_move.move_line_ids.quantity_product_uom, 1.0, 'The out move should be reserved')
|
||||
|
||||
def test_assign_done_sml_and_validate_it(self):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user