[FIX] stock: move line: decrease reserved quantity
When we decrease the reserved quantity, the argument 'strict' must be set to 'True', else we might unreserve quants of another move line. This behavior has been lost in rev: https://github.com/odoo/odoo/commit/9bb696e13abcbf95c9552fab60b1dfb997afaa59 We introduce a test to show where an issue happened without this patch: have tracked and untracked quants for the same product in stock, reserve them and edit the untracked move line in order to set a lot to it. The first step will be to unreserve the move line without lot, and if we do not pass a Strict kw, stock.quant's `_gather` will return quants with lot and unreserve on them. Everything is now inconsistent and lead to issues upon validation.
This commit is contained in:
committed by
Simon Lejeune
parent
50794f64af
commit
7a39ab50c5
@@ -132,10 +132,10 @@ class StockMoveLine(models.Model):
|
||||
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
|
||||
qty_to_decrease = ml.product_qty - ml.product_uom_id._compute_quantity(vals['product_uom_qty'], ml.product_id.uom_id, rounding_method='HALF-UP')
|
||||
try:
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -qty_to_decrease, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -qty_to_decrease, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
except UserError:
|
||||
if ml.lot_id:
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -qty_to_decrease, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -qty_to_decrease, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
else:
|
||||
raise
|
||||
|
||||
@@ -156,10 +156,10 @@ class StockMoveLine(models.Model):
|
||||
for ml in self.filtered(lambda ml: ml.state in ['partially_available', 'assigned']):
|
||||
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
|
||||
try:
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
except UserError:
|
||||
if ml.lot_id:
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
else:
|
||||
raise
|
||||
|
||||
@@ -233,7 +233,7 @@ class StockMoveLine(models.Model):
|
||||
# Unlinking a pack operation should unreserve.
|
||||
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product' and not float_is_zero(ml.product_qty, precision_digits=precision):
|
||||
self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id,
|
||||
package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
return super(StockMoveLine, self).unlink()
|
||||
|
||||
def action_done(self):
|
||||
@@ -298,9 +298,9 @@ class StockMoveLine(models.Model):
|
||||
# unreserve what's been reserved
|
||||
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product' and ml.product_qty:
|
||||
try:
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
except UserError:
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
|
||||
# move what's been actually done
|
||||
quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP')
|
||||
|
||||
@@ -544,6 +544,53 @@ class StockMove(TransactionCase):
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location), 0.0)
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location)), 0.0)
|
||||
|
||||
def test_mixed_tracking_reservation_7(self):
|
||||
""" Similar test_mixed_tracking_reservation_2 but creates first the tracked quant, then the
|
||||
untracked ones. When adding a lot to the untracked move line, it should not decrease the
|
||||
untracked quant then increase a non-existing tracked one that will fallback on the
|
||||
untracked quant.
|
||||
"""
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': self.product2.id,
|
||||
})
|
||||
lot2 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot2',
|
||||
'product_id': self.product2.id,
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, lot_id=lot1)
|
||||
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1)
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location), 2.0)
|
||||
# creation
|
||||
move1 = self.env['stock.move'].create({
|
||||
'name': 'test_in_1',
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.customer_location.id,
|
||||
'product_id': self.product2.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 2.0,
|
||||
})
|
||||
move1.action_confirm()
|
||||
move1.action_assign()
|
||||
self.assertEqual(len(move1.move_line_ids), 2)
|
||||
for ml in move1.move_line_ids:
|
||||
self.assertEqual(ml.product_qty, 1.0)
|
||||
|
||||
untracked_move_line = move1.move_line_ids.filtered(lambda ml: not ml.lot_id).lot_id = lot2
|
||||
for ml in move1.move_line_ids:
|
||||
self.assertEqual(ml.product_qty, 1.0)
|
||||
|
||||
move1.move_line_ids.write({'qty_done': 1.0})
|
||||
|
||||
move1.action_done()
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location), 0.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, lot_id=lot1, strict=True), 0.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, lot_id=lot2, strict=True), 0.0)
|
||||
quants = self.env['stock.quant']._gather(self.product2, self.stock_location)
|
||||
self.assertEqual(len(quants), 0)
|
||||
|
||||
def test_putaway_1(self):
|
||||
""" Receive products from a supplier. Check that putaway rules are rightly applied on
|
||||
the receipt move line.
|
||||
|
||||
@@ -475,7 +475,7 @@ class StockQuant(TransactionCase):
|
||||
})
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 1)
|
||||
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -10.0)
|
||||
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -10.0, strict=True)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 10.0)
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 1)
|
||||
|
||||
@@ -491,7 +491,7 @@ class StockQuant(TransactionCase):
|
||||
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 1.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
|
||||
with self.assertRaises(UserError):
|
||||
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -1.0)
|
||||
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -1.0, strict=True)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
|
||||
|
||||
def test_action_done_1(self):
|
||||
@@ -505,7 +505,7 @@ class StockQuant(TransactionCase):
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
|
||||
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 2.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
|
||||
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -2.0)
|
||||
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -2.0, strict=True)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
|
||||
self.env['stock.quant']._update_available_quantity(product1, stock_location, -2.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
|
||||
@@ -545,7 +545,7 @@ class StockQuant(TransactionCase):
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location, lot_id=lot1), 1.0)
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -1.0)
|
||||
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -1.0, strict=True)
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location, strict=True), 1.0)
|
||||
|
||||
Reference in New Issue
Block a user