diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index dd0635a11f8..2f692feaec3 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -369,14 +369,7 @@ class StockMoveLine(models.Model): # Unreserve the old charateristics of the move line. if not ml.move_id._should_bypass_reservation(ml.location_id): - try: - Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.reserved_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) - except UserError: - # If we were not able to unreserve on tracked quants, we can use untracked ones. - if ml.lot_id: - Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.reserved_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) - else: - raise + Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.reserved_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) # Reserve the maximum available of the new charateristics of the move line. if not ml.move_id._should_bypass_reservation(updates.get('location_id', ml.location_id)): @@ -386,14 +379,7 @@ class StockMoveLine(models.Model): package_id=updates.get('package_id', ml.package_id), owner_id=updates.get('owner_id', ml.owner_id), strict=True) reserved_qty = sum([x[1] for x in q]) except UserError: - if updates.get('lot_id'): - # If we were not able to reserve on tracked quants, we can use untracked ones. - try: - q = Quant._update_reserved_quantity(ml.product_id, updates.get('location_id', ml.location_id), new_reserved_uom_qty, lot_id=False, - package_id=updates.get('package_id', ml.package_id), owner_id=updates.get('owner_id', ml.owner_id), strict=True) - reserved_qty = sum([x[1] for x in q]) - except UserError: - pass + pass if reserved_qty != new_reserved_uom_qty: new_reserved_uom_qty = ml.product_id.uom_id._compute_quantity(reserved_qty, ml.product_uom_id, rounding_method='HALF-UP') moves_to_recompute_state |= ml.move_id @@ -481,13 +467,7 @@ class StockMoveLine(models.Model): for ml in self: # Unlinking a move line should unreserve. if not float_is_zero(ml.reserved_qty, precision_digits=precision) and not ml.move_id._should_bypass_reservation(ml.location_id): - try: - self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.reserved_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) - except UserError: - if ml.lot_id: - self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.reserved_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) - elif not self.env.context.get(MODULE_UNINSTALL_FLAG, False): - raise # pylint: disable=raise-unlink-override + self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.reserved_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) moves = self.mapped('move_id') res = super(StockMoveLine, self).unlink() if moves: @@ -588,10 +568,7 @@ class StockMoveLine(models.Model): 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_ids_to_ignore=ml_ids_to_ignore) # unreserve what's been reserved if not ml.move_id._should_bypass_reservation(ml.location_id) and ml.product_id.type == 'product' and ml.reserved_qty: - try: - Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.reserved_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.reserved_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) + Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.reserved_qty, lot_id=ml.lot_id, 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') diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index 2a62632b91e..995867c5787 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -256,6 +256,8 @@ class StockQuant(models.Model): package_id = self.env['stock.quant.package'].browse(vals.get('package_id')) owner_id = self.env['res.partner'].browse(vals.get('owner_id')) quant = self._gather(product, location, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True) + if lot_id: + quant = quant.filtered(lambda q: q.lot_id) if quant: quant = quant[0].sudo() @@ -536,19 +538,19 @@ class StockQuant(models.Model): domain = [('product_id', '=', product_id.id)] if not strict: if lot_id: - domain = expression.AND([[('lot_id', '=', lot_id.id)], domain]) + domain = expression.AND([['|', ('lot_id', '=', lot_id.id), ('lot_id', '=', False)], domain]) if package_id: domain = expression.AND([[('package_id', '=', package_id.id)], domain]) if owner_id: domain = expression.AND([[('owner_id', '=', owner_id.id)], domain]) domain = expression.AND([[('location_id', 'child_of', location_id.id)], domain]) else: - domain = expression.AND([[('lot_id', '=', lot_id and lot_id.id or False)], domain]) + domain = expression.AND([['|', ('lot_id', '=', lot_id.id), ('lot_id', '=', False)] if lot_id else [('lot_id', '=', False)], domain]) domain = expression.AND([[('package_id', '=', package_id and package_id.id or False)], domain]) domain = expression.AND([[('owner_id', '=', owner_id and owner_id.id or False)], domain]) domain = expression.AND([[('location_id', '=', location_id.id)], domain]) - return self.search(domain, order=removal_strategy_order) + return self.search(domain, order=removal_strategy_order).sorted(lambda q: not q.lot_id) @api.model def _get_available_quantity(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False, allow_negative=False): @@ -696,6 +698,8 @@ class StockQuant(models.Model): """ self = self.sudo() quants = self._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True) + if lot_id and quantity > 0: + quants = quants.filtered(lambda q: q.lot_id) if location_id.should_bypass_reservation(): incoming_dates = [] @@ -714,7 +718,7 @@ class StockQuant(models.Model): quant = None if quants: # see _acquire_one_job for explanations - self._cr.execute("SELECT id FROM stock_quant WHERE id IN %s LIMIT 1 FOR NO KEY UPDATE SKIP LOCKED", [tuple(quants.ids)]) + self._cr.execute("SELECT id FROM stock_quant WHERE id IN %s ORDER BY lot_id LIMIT 1 FOR NO KEY UPDATE SKIP LOCKED", [tuple(quants.ids)]) stock_quant_result = self._cr.fetchone() if stock_quant_result: quant = self.browse(stock_quant_result[0]) @@ -756,7 +760,7 @@ class StockQuant(models.Model): if float_compare(quantity, 0, precision_rounding=rounding) > 0: # if we want to reserve - available_quantity = self._get_available_quantity(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict) + available_quantity = sum(quants.filtered(lambda q: float_compare(q.quantity, 0, precision_rounding=rounding) > 0).mapped('quantity')) - sum(quants.mapped('reserved_quantity')) if float_compare(quantity, available_quantity, precision_rounding=rounding) > 0: raise UserError(_('It is not possible to reserve more products of %s than you have in stock.', product_id.display_name)) elif float_compare(quantity, 0, precision_rounding=rounding) < 0: diff --git a/addons/stock/tests/test_inventory.py b/addons/stock/tests/test_inventory.py index 1c3defee8bd..f62dbf1fed2 100644 --- a/addons/stock/tests/test_inventory.py +++ b/addons/stock/tests/test_inventory.py @@ -155,10 +155,10 @@ class TestInventory(TransactionCase): stock_confirmation_wizard.action_confirm() # check - self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, lot_id=lot1, strict=True), 1.0) + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, lot_id=lot1, strict=True), 11.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, strict=True), 10.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location), 11.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location, lot_id=lot1, strict=True)), 1.0) + self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location, lot_id=lot1, strict=True).filtered(lambda q: q.lot_id)), 1.0) self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location, strict=True)), 1.0) self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location)), 2.0) diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 6d344044aa4..1724c5d6480 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -354,10 +354,10 @@ class StockMove(TransactionCase): # no changes on quants, even if i made some move lines with a lot id whom reserved on untracked quants self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, strict=True)), 1.0) # with a qty of 2 - self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot1, strict=True)), 1.0) - self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot2, strict=True)), 1.0) - self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot3, strict=True)), 0) - self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot4, strict=True)), 0) + self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot1, strict=True).filtered(lambda q: q.lot_id)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot2, strict=True).filtered(lambda q: q.lot_id)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot3, strict=True).filtered(lambda q: q.lot_id)), 0) + self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot4, strict=True).filtered(lambda q: q.lot_id)), 0) move1.move_line_ids.write({'qty_done': 1.0}) @@ -667,7 +667,7 @@ class StockMove(TransactionCase): self.assertEqual(move1.reserved_availability, 0.0) self.assertEqual(len(move1.move_line_ids), 0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, strict=True), 1.0) - self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1, strict=True), 1.0) + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1, strict=True), 2.0) def test_putaway_1(self): """ Receive products from a supplier. Check that putaway rules are rightly applied on diff --git a/addons/stock/tests/test_quant.py b/addons/stock/tests/test_quant.py index d00b31ff01d..b8e50473bf3 100644 --- a/addons/stock/tests/test_quant.py +++ b/addons/stock/tests/test_quant.py @@ -471,26 +471,26 @@ class StockQuant(TransactionCase): self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 2.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, strict=True), 1.0) - self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1), 1.0) + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1), 2.0) self.env['stock.quant']._update_reserved_quantity(self.product_serial, self.stock_location, 1.0, lot_id=lot1, strict=True) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, strict=True), 1.0) - self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1), 0.0) + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1), 1.0) self.env['stock.quant']._update_reserved_quantity(self.product_serial, self.stock_location, -1.0, lot_id=lot1, strict=True) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 2.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, strict=True), 1.0) - self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1), 1.0) + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1), 2.0) with self.assertRaises(UserError): self.env['stock.quant']._update_reserved_quantity(self.product_serial, self.stock_location, -1.0, strict=True) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 2.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, strict=True), 1.0) - self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1), 1.0) + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1), 2.0) def test_access_rights_1(self): """ Directly update the quant with a user with or without stock access rights should not raise diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index 1bdb886b85b..b299c19bc94 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -1124,7 +1124,7 @@ class TestStockFlow(TestStockCommon): self.assertEqual(lotproduct.qty_available, 10, "Wrong qty available for lotproduct") quants = self.StockQuantObj.search([('product_id', '=', lotproduct.id), ('location_id', '=', self.stock_location), ('lot_id', '=', lot1.id)]) total_qty = sum([quant.quantity for quant in quants]) - self.assertEqual(total_qty, 10, 'Expecting 0 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty)) + self.assertEqual(total_qty, 10, 'Expecting 10 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty)) quants = self.StockQuantObj.search([('product_id', '=', lotproduct.id), ('location_id', '=', self.stock_location), ('lot_id', '=', False)]) total_qty = sum([quant.quantity for quant in quants]) self.assertEqual(total_qty, 0, 'Expecting 0 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty))