diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index a8f5e05ae7660a17e8e6dff4ff55bb48a3f5da71..84ac17a6b96825813e9e3b902fb54477056a14ad 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -531,6 +531,8 @@ class Product(models.Model): owner_id = self.env['res.partner'].browse(owner_id) to_uom = self.env['uom.uom'].browse(to_uom) quants = self.env['stock.quant']._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True) + if lot_id: + quants = quants.filtered(lambda q: q.lot_id == lot_id) theoretical_quantity = sum([quant.quantity for quant in quants]) if to_uom and product_id.uom_id != to_uom: theoretical_quantity = product_id.uom_id._compute_quantity(theoretical_quantity, to_uom) diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 68b193305d292b4e698c2543c56dd105643fb660..633e1250fb5e94ad07f1b740164348729de41652 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -281,14 +281,7 @@ class StockMoveLine(models.Model): # Unreserve the old charateristics of the move line. if not ml._should_bypass_reservation(ml.location_id): - 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, 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.product_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.product_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._should_bypass_reservation(updates.get('location_id', ml.location_id)): @@ -298,14 +291,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_product_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_product_uom_qty: new_product_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 @@ -388,13 +374,7 @@ class StockMoveLine(models.Model): raise UserError(_('You can not delete product moves if the picking is done. You can only correct the done quantities.')) # Unlinking a move line should unreserve. if ml.product_id.type == 'product' and not ml._should_bypass_reservation(ml.location_id) and not float_is_zero(ml.product_qty, precision_digits=precision): - try: - 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, strict=True) - except UserError: - if ml.lot_id: - self.env['stock.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 + 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, strict=True) moves = self.mapped('move_id') res = super(StockMoveLine, self).unlink() if moves: @@ -485,10 +465,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_to_ignore=ml_to_ignore) # unreserve what's been reserved if not ml._should_bypass_reservation(ml.location_id) 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, 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, strict=True) + 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) # 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 f8d5ed09a6da6eba0867aff50b5b9f548f2d30ba..21c0d69bc3644edbb217d50551af645099df4714 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -280,14 +280,14 @@ class StockQuant(models.Model): ] 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]) @@ -302,7 +302,9 @@ class StockQuant(models.Model): self._cr.execute(query_str, where_clause_params) res = self._cr.fetchall() # No uniquify list necessary as auto_join is not applied anyways... - return self.browse([x[0] for x in res]) + quants = self.browse([x[0] for x in res]) + quants = quants.sorted(lambda q: not q.lot_id) + return quants @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): @@ -401,6 +403,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) incoming_dates = [d for d in quants.mapped('in_date') if d] incoming_dates = [fields.Datetime.from_string(incoming_date) for incoming_date in incoming_dates] @@ -462,7 +466,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 b787b21a423ec1f1943a9d4a542d92a5239cd6bb..d09cfb4871523417cbdb4d534fca29dfc9465c48 100644 --- a/addons/stock/tests/test_inventory.py +++ b/addons/stock/tests/test_inventory.py @@ -152,10 +152,10 @@ class TestInventory(SavepointCase): wizard_warning_lot.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 3a94fb83d07b2da4dc993e83bf7705e12c55c223..b6df2ff3aabd9bcb64b9bf2d1df76cdc135e0bcb 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -365,10 +365,10 @@ class StockMove(SavepointCase): # 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}) @@ -678,7 +678,7 @@ class StockMove(SavepointCase): 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 776a9a542b171b6ca680ed5d9df9c19a74036c0b..67ff098c9a0a06ec444a2a8ffb1a35cd19912c86 100644 --- a/addons/stock/tests/test_quant.py +++ b/addons/stock/tests/test_quant.py @@ -462,26 +462,26 @@ class StockQuant(SavepointCase): 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 sould raise