diff options
| author | Arnold Moyaux <arm@odoo.com> | 2021-01-26 11:13:22 +0000 |
|---|---|---|
| committer | Arnold Moyaux <arm@odoo.com> | 2021-02-17 11:56:44 +0000 |
| commit | 572ba1be7d8b47192c98aec10cfe1b0b3dbbeb45 (patch) | |
| tree | 3306fd4420bdf70ee1dda5776b3ab0419845e4c3 /addons | |
| parent | f816c326f348ff78a982f91a736cc005d3e0cd31 (diff) | |
[FIX] stock: couldn't unreserve mixed tracking stock
- Install stock
- Go to Inventory > Configuration > Settings and enable "Lots" and "Storage Locations"
- Create a Product tracked By Lots (i.e. Product X)
- Go to Inventory > Operations > Inventory Adjustments
- Create an Inventory Adjustment for Product X:
Product | Location | Lot/SN | Real Quantity
-------------------------------------------------------------
Product X | WH/Stock | LOT 01 | 20
Product X | WH/Stock | | 10
- Validate Inventory
- Go to Inventory > Operations > Transfers and create one:
* Source Location: WH/Stock
* Destination Location: WH/Stock/Shelf1
* Operation Type: Internal Transfers
* Operations:
[Product: Product X, Initial Demand: 25]
- Save Transfer, Mark As Todo and Check availability
- Click on list icon of Operation line for Product X to display Detailed Operations
- 20 units of LOT 01 and 5 units without lot have been reserved
- Set LOT 01 for the 5 reserved units without lot and confirm
- Open Detailed Operations again
- There are now 20 units of LOT 01 and 5 units of LOT 01
- Remove the row with 5 units and confirm
- Check availability and open Detailed Operation
- There is now only a row with 25 reserved units of LOT 01
- Unreserve
The following errror is raised:
"It is not possible to unreserve more products of P than you have in stock."
It happens because the system is not able to manage quants with lots and
wihtout lots at the same time. When modifying the move line to 25
reserved units. It's composed of 20 quants with lot and 5 quants without
lot. And when unreserving it will check if there is a quants with 25
units with the lot and if it's not found 25 units without lot. But never
25 units of quants with lots and without lots.
opw-2419444
Close #64497
closes odoo/odoo#66347
X-original-commit: 3b59d0b12bc9a80e325aa07d004ed9cf9d85f857
Signed-off-by: Arnold Moyaux <amoyaux@users.noreply.github.com>
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/stock/models/product.py | 2 | ||||
| -rw-r--r-- | addons/stock/models/stock_move_line.py | 31 | ||||
| -rw-r--r-- | addons/stock/models/stock_quant.py | 12 | ||||
| -rw-r--r-- | addons/stock/tests/test_inventory.py | 4 | ||||
| -rw-r--r-- | addons/stock/tests/test_move.py | 10 | ||||
| -rw-r--r-- | addons/stock/tests/test_quant.py | 8 |
6 files changed, 25 insertions, 42 deletions
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index 345fa28141c..e4d046c4fb5 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -512,6 +512,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 fce329740ad..5fec0ffb769 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -297,14 +297,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)): @@ -314,14 +307,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 @@ -404,13 +390,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: @@ -515,10 +495,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 fca233509f2..f9cdeabcd28 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -278,14 +278,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]) @@ -300,7 +300,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): @@ -399,6 +401,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] @@ -460,7 +464,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 3ef6e389e92..8b7c6d0a0ea 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 97dc10346fb..5aca352da75 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -352,10 +352,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}) @@ -665,7 +665,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 703567da024..cfcab420a25 100644 --- a/addons/stock/tests/test_quant.py +++ b/addons/stock/tests/test_quant.py @@ -464,26 +464,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 |
