From c0d7a05730dca242ad60192b9905891e3afa9a90 Mon Sep 17 00:00:00 2001 From: Arnold Moyaux Date: Fri, 23 Mar 2018 11:38:15 +0100 Subject: [PATCH] [REF] stock: `_action_done` and cache invalidation `_action_done` could unlink a quant. This happens when moving a product from a location to another one and makes null the quantity of this product in one of theses locations. In this case, we unlink the quant because it is now useless and could be confusing on reports based on quants. The unwanted side effect is that unlinking a record will invalidate the cache. In some pathologic cases, like making an inventory adjustment of 600 products and reseting their quantity back to 0, the time of the operation is around 15 minutes. With this patch that tries to work carefully with the cache invalidation, the same operations takes around 15 secondes. This commit do not unlink quant anymore, instead it use a 'garbage collector' on quants without quantity. The garbage collector is trigger on the scheduler and when the user open the inventory view. --- .../test_warehouse_multistep_manufacturing.py | 4 +- addons/stock/models/stock_inventory.py | 2 +- addons/stock/models/stock_move.py | 3 +- addons/stock/models/stock_move_line.py | 3 +- addons/stock/models/stock_quant.py | 23 ++- addons/stock/models/stock_rule.py | 1 + addons/stock/tests/test_inventory.py | 2 +- addons/stock/tests/test_move.py | 133 ++++++++++-------- addons/stock/tests/test_move2.py | 4 +- addons/stock/tests/test_packing_neg.py | 2 +- addons/stock/tests/test_quant.py | 36 ++--- addons/stock/tests/test_stock_flow.py | 18 +-- addons/stock/tests/test_warehouse.py | 8 +- addons/stock/tests/test_wise_operator.py | 3 +- addons/stock/wizard/stock_quantity_history.py | 1 + .../tests/test_batch_picking.py | 20 +-- 16 files changed, 146 insertions(+), 117 deletions(-) diff --git a/addons/mrp/tests/test_warehouse_multistep_manufacturing.py b/addons/mrp/tests/test_warehouse_multistep_manufacturing.py index a8396bdeeb7..08bce4479b3 100644 --- a/addons/mrp/tests/test_warehouse_multistep_manufacturing.py +++ b/addons/mrp/tests/test_warehouse_multistep_manufacturing.py @@ -140,7 +140,7 @@ class TestMultistepManufacturingWarehouse(TestMrpCommon): picking_stock_preprod.move_line_ids.qty_done = 4 picking_stock_preprod.action_done() - self.assertFalse(self.env['stock.quant']._gather(self.raw_product, self.warehouse.lot_stock_id)) + self.assertFalse(sum(self.env['stock.quant']._gather(self.raw_product, self.warehouse.lot_stock_id).mapped('quantity'))) self.assertTrue(self.env['stock.quant']._gather(self.raw_product, self.warehouse.pbm_loc_id)) production_order.action_assign() @@ -156,7 +156,7 @@ class TestMultistepManufacturingWarehouse(TestMrpCommon): product_produce.do_produce() production_order.button_mark_done() - self.assertFalse(self.env['stock.quant']._gather(self.raw_product, self.warehouse.pbm_loc_id)) + self.assertFalse(sum(self.env['stock.quant']._gather(self.raw_product, self.warehouse.pbm_loc_id).mapped('quantity'))) self.assertEqual(picking_stock_postprod.state, 'assigned') diff --git a/addons/stock/models/stock_inventory.py b/addons/stock/models/stock_inventory.py index 5c99471de96..486431d1818 100644 --- a/addons/stock/models/stock_inventory.py +++ b/addons/stock/models/stock_inventory.py @@ -233,7 +233,7 @@ class Inventory(models.Model): def _get_inventory_lines_values(self): # TDE CLEANME: is sql really necessary ? I don't think so locations = self.env['stock.location'].search([('id', 'child_of', [self.location_id.id])]) - domain = ' location_id in %s' + domain = ' location_id in %s AND quantity != 0' args = (tuple(locations.ids),) vals = [] diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 4fa38880b58..0b10b239fac 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -1124,7 +1124,7 @@ class StockMove(models.Model): except UserError: pass move._unreserve_initial_demand(new_move) - move.move_line_ids._action_done() + moves_todo.mapped('move_line_ids')._action_done() # Check the consistency of the result packages; there should be an unique location across # the contained quants. for result_package in moves_todo\ @@ -1142,7 +1142,6 @@ class StockMove(models.Model): if picking: picking._create_backorder() - return moves_todo def unlink(self): diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 817932a1d8b..d052f925413 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -304,6 +304,7 @@ class StockMoveLine(models.Model): if not location_id.should_bypass_reservation(): ml._free_reservation(ml.product_id, location_id, untracked_qty, lot_id=False, package_id=package_id, owner_id=owner_id) Quant._update_available_quantity(product_id, location_dest_id, quantity, lot_id=lot_id, package_id=result_package_id, owner_id=owner_id, in_date=in_date) + # Unreserve and reserve following move in order to have the real reserved quantity on move_line. next_moves |= ml.move_id.move_dest_ids.filtered(lambda move: move.state not in ('done', 'cancel')) @@ -359,6 +360,7 @@ class StockMoveLine(models.Model): intended to be called when editing a `done` move (that's what the override of `write` here is done. """ + Quant = self.env['stock.quant'] # First, we loop over all the move lines to do a preliminary check: `qty_done` should not # be negative and, according to the presence of a picking type or a linked inventory @@ -412,7 +414,6 @@ class StockMoveLine(models.Model): done_ml = self.env['stock.move.line'] for ml in self - ml_to_delete: if ml.product_id.type == 'product': - Quant = self.env['stock.quant'] rounding = ml.product_uom_id.rounding # if this move line is force assigned, unreserve elsewhere if needed diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index 52f91dd424d..a4028a79f83 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -198,7 +198,6 @@ 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) - rounding = product_id.uom_id.rounding 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] @@ -219,9 +218,6 @@ class StockQuant(models.Model): 'quantity': quant.quantity + quantity, 'in_date': in_date, }) - # cleanup empty quants - if float_is_zero(quant.quantity, precision_rounding=rounding) and float_is_zero(quant.reserved_quantity, precision_rounding=rounding): - quant.unlink() break except OperationalError as e: if e.pgcode == '55P03': # could not obtain the lock @@ -292,6 +288,22 @@ class StockQuant(models.Model): break return reserved_quants + @api.model + def _unlink_zero_quants(self): + """ _update_available_quantity may leave quants with no + quantity and no reserved_quantity. It used to directly unlink + these zero quants but this proved to hurt the performance as + this method is often called in batch and each unlink invalidate + the cache. We defer the calls to unlink in this method. + """ + precision_digits = max(6, self.env.ref('product.decimal_product_uom').digits * 2) + # Use a select instead of ORM search for UoM robustness. + query = """SELECT id FROM stock_quant WHERE round(quantity::numeric, %s) = 0 AND round(reserved_quantity::numeric, %s) = 0;""" + params = (precision_digits, precision_digits) + self.env.cr.execute(query, params) + quant_ids = self.env['stock.quant'].browse([quant['id'] for quant in self.env.cr.dictfetchall()]) + quant_ids.sudo().unlink() + @api.model def _merge_quants(self): """ In a situation where one transaction is updating a quant via @@ -334,7 +346,8 @@ class QuantPackage(models.Model): name = fields.Char( 'Package Reference', copy=False, index=True, default=lambda self: self.env['ir.sequence'].next_by_code('stock.quant.package') or _('Unknown Pack')) - quant_ids = fields.One2many('stock.quant', 'package_id', 'Bulk Content', readonly=True) + quant_ids = fields.One2many('stock.quant', 'package_id', 'Bulk Content', readonly=True, + domain=['|', ('quantity', '!=', 0), ('reserved_quantity', '!=', 0)]) packaging_id = fields.Many2one( 'product.packaging', 'Package Type', index=True) location_id = fields.Many2one( diff --git a/addons/stock/models/stock_rule.py b/addons/stock/models/stock_rule.py index ab40de21166..60fc7a8465b 100644 --- a/addons/stock/models/stock_rule.py +++ b/addons/stock/models/stock_rule.py @@ -372,6 +372,7 @@ class ProcurementGroup(models.Model): # Merge duplicated quants self.env['stock.quant']._merge_quants() + self.env['stock.quant']._unlink_zero_quants() @api.model def run_scheduler(self, use_new_cursor=False, company_id=False): diff --git a/addons/stock/tests/test_inventory.py b/addons/stock/tests/test_inventory.py index 6f4c3a5ab6c..b37b1a17464 100644 --- a/addons/stock/tests/test_inventory.py +++ b/addons/stock/tests/test_inventory.py @@ -48,7 +48,7 @@ class TestInventory(TransactionCase): # check self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 0.0) + self.assertEqual(sum(self.env['stock.quant']._gather(self.product1, self.stock_location).mapped('quantity')), 0.0) def test_inventory_2(self): """ Check that adding a tracked product through an inventory adjustment work as expected. diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index b1810cd5193..dcc379d9725 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -42,6 +42,10 @@ class StockMove(TransactionCase): 'categ_id': self.env.ref('product.product_category_all').id, }) + def gather_relevant(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False): + quants = self.env['stock.quant']._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict) + return quants.filtered(lambda q: not (q.quantity == 0 and q.reserved_quantity == 0)) + def test_in_1(self): """ Receive products from a supplier. Check that a move line is created and that the reception correctly increase a single quant in stock. @@ -79,8 +83,8 @@ class StockMove(TransactionCase): self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.supplier_location), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.supplier_location, allow_negative=True), -100.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 100.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.supplier_location)), 1.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.supplier_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) def test_in_2(self): """ Receive 5 tracked products from a supplier. The create move line should have 5 @@ -118,12 +122,12 @@ class StockMove(TransactionCase): self.assertEqual(move1.state, 'done') self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.supplier_location), 0.0) - supplier_quants = self.env['stock.quant']._gather(self.product3, self.supplier_location) + supplier_quants = self.gather_relevant(self.product3, self.supplier_location) self.assertEqual(sum(supplier_quants.mapped('quantity')), -5.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location), 5.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product3, self.supplier_location)), 1.0) - quants = self.env['stock.quant']._gather(self.product3, self.stock_location) + self.assertEqual(len(self.gather_relevant(self.product3, self.supplier_location)), 1.0) + quants = self.gather_relevant(self.product3, self.stock_location) self.assertEqual(len(quants), 1.0) for quant in quants: self.assertNotEqual(quant.in_date, False) @@ -171,12 +175,12 @@ class StockMove(TransactionCase): # Quant balance should result with 5 quant in supplier and stock self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.supplier_location), 0.0) - supplier_quants = self.env['stock.quant']._gather(self.product2, self.supplier_location) + supplier_quants = self.gather_relevant(self.product2, self.supplier_location) self.assertEqual(sum(supplier_quants.mapped('quantity')), -5.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location), 5.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.supplier_location)), 5.0) - quants = self.env['stock.quant']._gather(self.product2, self.stock_location) + self.assertEqual(len(self.gather_relevant(self.product2, self.supplier_location)), 5.0) + quants = self.gather_relevant(self.product2, self.stock_location) self.assertEqual(len(quants), 5.0) for quant in quants: self.assertNotEqual(quant.in_date, False) @@ -187,7 +191,7 @@ class StockMove(TransactionCase): """ # make some stock self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 100) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 100.0) # creation @@ -211,7 +215,7 @@ class StockMove(TransactionCase): self.assertEqual(len(move1.move_line_ids), 1) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) # Should be a reserved quantity and thus a quant. - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) # fill the move line move_line = move1.move_line_ids[0] @@ -224,10 +228,10 @@ class StockMove(TransactionCase): self.assertEqual(move1.state, 'done') # Check there is one quant in customer location self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.customer_location), 100.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.customer_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.customer_location)), 1.0) # there should be no quant amymore in the stock location self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 0.0) def test_out_2(self): """ Send a consumable product to a client. Check that a move line is created but @@ -236,7 +240,7 @@ class StockMove(TransactionCase): # make some stock self.product1.type = 'consu' - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) # creation @@ -260,7 +264,7 @@ class StockMove(TransactionCase): self.assertEqual(len(move1.move_line_ids), 1) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) # Should be a reserved quantity and thus a quant. - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 0.0) # fill the move line move_line = move1.move_line_ids[0] @@ -273,10 +277,10 @@ class StockMove(TransactionCase): self.assertEqual(move1.state, 'done') # no quants are created in the customer location since it's a consumable self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.customer_location), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.customer_location)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.customer_location)), 0.0) # there should be no quant amymore in the stock location self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 0.0) def test_mixed_tracking_reservation_1(self): """ Send products tracked by lot to a customer. In your stock, there are tracked and @@ -354,11 +358,11 @@ class StockMove(TransactionCase): self.assertEqual(ml.product_qty, 1.0) # no changes on quants, even if i made some move lines with a lot id whom reserved on untracked quants - self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location, strict=True)), 1.0) # with a qty of 2 - 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=lot2, strict=True)), 1.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location, lot_id=lot3, strict=True)), 0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location, lot_id=lot4, strict=True)), 0) + self.assertEqual(len(self.gather_relevant(self.product2, self.stock_location, strict=True)), 1.0) # with a qty of 2 + self.assertEqual(len(self.gather_relevant(self.product2, self.stock_location, lot_id=lot1, strict=True)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product2, self.stock_location, lot_id=lot2, strict=True)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product2, self.stock_location, lot_id=lot3, strict=True)), 0) + self.assertEqual(len(self.gather_relevant(self.product2, self.stock_location, lot_id=lot4, strict=True)), 0) move1.move_line_ids.write({'qty_done': 1.0}) @@ -522,7 +526,7 @@ class StockMove(TransactionCase): # validating the move line should move the lot, not create a negative quant in stock move1._action_done() 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) + self.assertEqual(len(self.gather_relevant(self.product2, self.stock_location)), 0.0) def test_mixed_tracking_reservation_6(self): # create an untracked quant @@ -558,7 +562,7 @@ class StockMove(TransactionCase): # validating the move line should move the lot, not create a negative quant in stock move1._action_done() 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) + self.assertEqual(len(self.gather_relevant(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 @@ -604,7 +608,7 @@ class StockMove(TransactionCase): 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) + quants = self.gather_relevant(self.product2, self.stock_location) self.assertEqual(len(quants), 0) def test_mixed_tracking_reservation_8(self): @@ -852,7 +856,7 @@ class StockMove(TransactionCase): """ # make some stock self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 150.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) move1 = self.env['stock.move'].create({ 'name': 'test_putaway_1', @@ -864,7 +868,7 @@ class StockMove(TransactionCase): }) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 150.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) self.assertEqual(move1.availability, 100.0) def test_availability_2(self): @@ -873,7 +877,7 @@ class StockMove(TransactionCase): """ # make some stock self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 50.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) move1 = self.env['stock.move'].create({ 'name': 'test_putaway_1', @@ -885,7 +889,7 @@ class StockMove(TransactionCase): }) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 50.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) self.assertEqual(move1.availability, 50.0) def test_availability_3(self): @@ -948,9 +952,9 @@ class StockMove(TransactionCase): self.assertEqual(move1.move_line_ids.qty_done, 15) self.assertEqual(move2.state, 'done') - stock_quants = self.env['stock.quant']._gather(self.product1, self.stock_location) + stock_quants = self.gather_relevant(self.product1, self.stock_location) self.assertEqual(len(stock_quants), 0) - customer_quants = self.env['stock.quant']._gather(self.product1, self.customer_location) + customer_quants = self.gather_relevant(self.product1, self.customer_location) self.assertEqual(customer_quants.quantity, 30) self.assertEqual(customer_quants.reserved_quantity, 0) @@ -1072,7 +1076,7 @@ class StockMove(TransactionCase): self.assertEqual(move.product_uom.id, self.uom_dozen.id) self.assertEqual(move.state, 'done') self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.customer_location), 12.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.customer_location)), 12) + self.assertEqual(len(self.gather_relevant(self.product2, self.customer_location)), 12) def test_availability_8(self): """ Test the assignment mechanism when the product quantity is decreased on a partially @@ -1199,7 +1203,7 @@ class StockMove(TransactionCase): """ # make some stock self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2) # creation @@ -1223,7 +1227,7 @@ class StockMove(TransactionCase): self.assertEqual(len(move1.move_line_ids), 1) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) - quants = self.env['stock.quant']._gather(self.product1, self.stock_location) + quants = self.gather_relevant(self.product1, self.stock_location) self.assertEqual(len(quants), 1.0) self.assertEqual(quants.quantity, 2.0) self.assertEqual(quants.reserved_quantity, 2.0) @@ -1240,7 +1244,7 @@ class StockMove(TransactionCase): """ # make some stock self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2) # creation @@ -1264,7 +1268,7 @@ class StockMove(TransactionCase): self.assertEqual(len(move1.move_line_ids), 1) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) - quants = self.env['stock.quant']._gather(self.product1, self.stock_location) + quants = self.gather_relevant(self.product1, self.stock_location) self.assertEqual(len(quants), 1.0) self.assertEqual(quants.quantity, 2.0) self.assertEqual(quants.reserved_quantity, 2.0) @@ -1286,7 +1290,7 @@ class StockMove(TransactionCase): 'location_id': self.stock_location.id, 'quantity': 2, }) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 2) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 2) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 5) # creation @@ -1310,7 +1314,7 @@ class StockMove(TransactionCase): self.assertEqual(len(move1.move_line_ids), 1) move1._do_unreserve() - quants = self.env['stock.quant']._gather(self.product1, self.stock_location) + quants = self.gather_relevant(self.product1, self.stock_location) self.assertEqual(len(quants), 2.0) for quant in quants: self.assertEqual(quant.reserved_quantity, 0) @@ -1362,7 +1366,7 @@ class StockMove(TransactionCase): """ # make some stock self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) move_stock_pack = self.env['stock.move'].create({ 'name': 'test_link_assign_1_1', @@ -1404,7 +1408,7 @@ class StockMove(TransactionCase): # make some stock self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location, lot1)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location, lot1)), 1.0) move_stock_pack = self.env['stock.move'].create({ 'name': 'test_link_2_1', @@ -1431,18 +1435,18 @@ class StockMove(TransactionCase): move_line_stock_pack = move_stock_pack.move_line_ids[0] self.assertEqual(move_line_stock_pack.lot_id.id, lot1.id) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot1), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location, lot1)), 1.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.pack_location, lot1)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location, lot1)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.pack_location, lot1)), 0.0) move_line_stock_pack.qty_done = 1.0 move_stock_pack._action_done() self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot1), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location, lot1)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location, lot1)), 0.0) move_line_pack_cust = move_pack_cust.move_line_ids[0] self.assertEqual(move_line_pack_cust.lot_id.id, lot1.id) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.pack_location, lot_id=lot1), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.pack_location, lot1)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.pack_location, lot1)), 1.0) def test_link_assign_3(self): """ Test the assignment mechanism when three chained stock moves (2 sources, 1 dest) try to @@ -1450,7 +1454,7 @@ class StockMove(TransactionCase): """ # make some stock self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) move_stock_pack_1 = self.env['stock.move'].create({ 'name': 'test_link_assign_1_1', @@ -1494,8 +1498,8 @@ class StockMove(TransactionCase): self.assertEqual(move_pack_cust.state, 'partially_available') self.assertEqual(len(move_pack_cust.move_line_ids), 1) # Should have 1 quant in stock_location and another in pack_location - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.pack_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.pack_location)), 1.0) move_stock_pack_2._action_assign() self.assertEqual(move_stock_pack_2.state, 'assigned') @@ -1504,8 +1508,8 @@ class StockMove(TransactionCase): move_stock_pack_2._action_done() self.assertEqual(move_stock_pack_2.state, 'done') - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.pack_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.pack_location)), 1.0) self.assertEqual(move_pack_cust.state, 'assigned') self.assertEqual(len(move_pack_cust.move_line_ids), 1) @@ -1526,7 +1530,7 @@ class StockMove(TransactionCase): # make some stock self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2.0, lot_id=lot1) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location, lot1)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location, lot1)), 1.0) move_stock_pack_1 = self.env['stock.move'].create({ 'name': 'test_link_assign_1_1', @@ -1960,7 +1964,7 @@ class StockMove(TransactionCase): # stock location: 2.0 # pack location: -1.0 self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 1.0) move_out = self.env['stock.move'].create({ 'name': 'test_link_assign_out', @@ -1974,7 +1978,7 @@ class StockMove(TransactionCase): move_out._action_assign() move_out.quantity_done = 1.0 move_out._action_done() - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.pack_location)), 1.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.pack_location)), 1.0) move_stock_pack = self.env['stock.move'].create({ 'name': 'test_link_assign_1_1', @@ -2433,8 +2437,8 @@ class StockMove(TransactionCase): self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location, lot_id=lot1, strict=True), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product3, self.stock_location)), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product3, self.stock_location, lot_id=lot1, strict=True)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product3, self.stock_location)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product3, self.stock_location, lot_id=lot1, strict=True)), 0.0) def test_edit_reserved_move_line_8(self): """ Send 5 tracked products to a client, but some of these products do not have any lot set @@ -2499,9 +2503,9 @@ class StockMove(TransactionCase): self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location, lot_id=lot1, strict=True), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location, lot_id=lot2, strict=True), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product3, self.stock_location)), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product3, self.stock_location, lot_id=lot1, strict=True)), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product3, self.stock_location, lot_id=lot2, strict=True)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product3, self.stock_location)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product3, self.stock_location, lot_id=lot1, strict=True)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product3, self.stock_location, lot_id=lot2, strict=True)), 0.0) def test_edit_done_move_line_1(self): """ Test that editing a done stock move line linked to an untracked product correctly and @@ -2907,13 +2911,13 @@ class StockMove(TransactionCase): move1.move_line_ids.qty_done = 10 move1._action_done() - quant = self.env['stock.quant']._gather(self.product1, self.stock_location) + quant = self.gather_relevant(self.product1, self.stock_location) self.assertEqual(len(quant), 1.0) # edit once done, we actually moved 2 products move1.move_line_ids.qty_done = 0 - quant = self.env['stock.quant']._gather(self.product1, self.stock_location) + quant = self.gather_relevant(self.product1, self.stock_location) self.assertEqual(len(quant), 0.0) self.assertEqual(move1.product_uom_qty, 0.0) @@ -3098,7 +3102,7 @@ class StockMove(TransactionCase): self.assertEqual(picking.move_lines.state, 'done') self.assertEqual(picking.move_lines.quantity_done, 5.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 0.0) # The backoder should contain a move for the other 5 produts. backorder = self.env['stock.picking'].search([('backorder_id', '=', picking.id)]) @@ -3210,7 +3214,7 @@ class StockMove(TransactionCase): self.assertEqual(picking.move_lines.move_line_ids.qty_done, 5.0) # Check quants data self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 0.0) + self.assertEqual(len(self.gather_relevant(self.product1, self.stock_location)), 0.0) def _create_picking_test_immediate_validate_5(self, picking_type_id, product_id): picking = self.env['stock.picking'].create({ @@ -3541,7 +3545,7 @@ class StockMove(TransactionCase): move1.move_line_ids.qty_done = 1 move1._action_done() - quant = self.env['stock.quant']._gather(self.product3, self.stock_location) + quant = self.gather_relevant(self.product3, self.stock_location) self.assertEqual(len(quant), 1.0) self.assertNotEqual(quant.in_date, False) @@ -3561,7 +3565,7 @@ class StockMove(TransactionCase): move2.move_line_ids.qty_done = 1 move2._action_done() - quant = self.env['stock.quant']._gather(self.product3, self.pack_location) + quant = self.gather_relevant(self.product3, self.pack_location) self.assertEqual(len(quant), 1.0) self.assertEqual(quant.in_date, initial_incoming_date) @@ -3658,6 +3662,7 @@ class StockMove(TransactionCase): ('location_id.usage', '=', 'internal'), ('product_id', '=', self.product3.id), ('lot_id', '=', lot1.id), + ('quantity', '!=', 0), ]) self.assertEqual(quant_lot1.location_id, self.stock_location) self.assertAlmostEqual(quant_lot1.in_date, initial_in_date_lot1, delta=timedelta(seconds=1)) @@ -3667,6 +3672,7 @@ class StockMove(TransactionCase): ('location_id.usage', '=', 'internal'), ('product_id', '=', self.product3.id), ('lot_id', '=', lot2.id), + ('quantity', '!=', 0), ]) self.assertEqual(quant_lot2.location_id, self.pack_location) self.assertAlmostEqual(quant_lot2.in_date, initial_in_date_lot2, delta=timedelta(seconds=1)) @@ -3719,6 +3725,7 @@ class StockMove(TransactionCase): ('location_id', '=', self.stock_location.id), ('product_id', '=', self.product3.id), ('lot_id', '=', lot2.id), + ('quantity', '!=', 0), ]).in_date # Edit lot1's incoming date. @@ -3726,6 +3733,7 @@ class StockMove(TransactionCase): ('location_id.usage', '=', 'internal'), ('product_id', '=', self.product3.id), ('lot_id', '=', lot1.id), + ('quantity', '!=', 0), ]) from odoo.fields import Datetime from datetime import timedelta @@ -3760,6 +3768,7 @@ class StockMove(TransactionCase): quants = self.env['stock.quant'].search([ ('location_id.usage', '=', 'internal'), ('product_id', '=', self.product3.id), + ('quantity', '!=', 0), ]) self.assertEqual(len(quants), 2) for quant in quants: diff --git a/addons/stock/tests/test_move2.py b/addons/stock/tests/test_move2.py index 6ae9b1bc118..9249fc5f0ac 100644 --- a/addons/stock/tests/test_move2.py +++ b/addons/stock/tests/test_move2.py @@ -159,7 +159,7 @@ class TestPickShip(TestStockCommon): self.assertEqual(picking_client.state, 'assigned', 'The picking should not assign what it does not have') self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, stock_location), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 5.0) - self.assertEqual(len(self.env['stock.quant']._gather(self.productA, stock_location)), 0.0) + self.assertEqual(sum(self.env['stock.quant']._gather(self.productA, stock_location).mapped('quantity')), 0.0) self.assertEqual(len(self.env['stock.quant']._gather(self.productA, pack_location)), 1.0) def test_mto_moves_return(self): @@ -681,7 +681,7 @@ class TestPickShip(TestStockCommon): self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pick_location), 5.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, return_location), 5.0) - self.assertEqual(len(self.env['stock.quant'].search([('product_id', '=', self.productA.id)])), 2) + self.assertEqual(len(self.env['stock.quant'].search([('product_id', '=', self.productA.id), ('quantity', '!=', 0)])), 2) class TestSinglePicking(TestStockCommon): diff --git a/addons/stock/tests/test_packing_neg.py b/addons/stock/tests/test_packing_neg.py index 325f9012f9f..13a897c231b 100644 --- a/addons/stock/tests/test_packing_neg.py +++ b/addons/stock/tests/test_packing_neg.py @@ -125,7 +125,7 @@ class TestPackingNeg(TransactionCase): # Check the quants that you have -20 pieces pallet 2 in stock, and a total quantity # of 50 in stock from pallet 3 (should be 20+30, as it has been split by reservation) - records = self.env['stock.quant'].search([('product_id', '=', product_neg.id)]) + records = self.env['stock.quant'].search([('product_id', '=', product_neg.id), ('quantity', '!=', '0')]) pallet_3_stock_qty = 0 for rec in records: if rec.package_id.name == 'Palneg 2' and rec.location_id.id == self.ref('stock.stock_location_stock'): diff --git a/addons/stock/tests/test_quant.py b/addons/stock/tests/test_quant.py index 749c10e57e3..49fcb6702cb 100644 --- a/addons/stock/tests/test_quant.py +++ b/addons/stock/tests/test_quant.py @@ -27,6 +27,10 @@ class StockQuant(TransactionCase): 'groups_id': [(6, 0, [self.env.ref('stock.group_stock_user').id])] }) + def gather_relevant(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False): + quants = self.env['stock.quant']._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict) + return quants.filtered(lambda q: not (q.quantity == 0 and q.reserved_quantity == 0)) + def test_get_available_quantity_1(self): """ Quantity availability with only one quant in a location. """ @@ -166,7 +170,7 @@ class StockQuant(TransactionCase): 'type': 'consu', }) 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)), 0) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 0) with self.assertRaises(ValidationError): self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0) @@ -214,7 +218,7 @@ class StockQuant(TransactionCase): self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0) self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 3.0) - self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 2) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 2) def test_increase_available_quantity_3(self): """ Increase the available quantity when a concurrent transaction is already increasing @@ -233,7 +237,7 @@ class StockQuant(TransactionCase): self.env['stock.quant']._update_available_quantity(product, stock_location, 1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(product, stock_location), available_quantity + 1) - self.assertEqual(len(self.env['stock.quant']._gather(product, stock_location, strict=True)), 2) + self.assertEqual(len(self.gather_relevant(product, stock_location, strict=True)), 2) def test_increase_available_quantity_4(self): """ Increase the available quantity when no quants are already in a location with a user without access right. @@ -324,10 +328,10 @@ class StockQuant(TransactionCase): 'quantity': 1.0, }) self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0) - self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 2) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 2) self.env['stock.quant']._update_available_quantity(product1, stock_location, -1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 1.0) - self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 1) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 1) def test_decrease_available_quantity_3(self): """ Decrease the available quantity when a concurrent transaction is already increasing @@ -345,7 +349,7 @@ class StockQuant(TransactionCase): self.env['stock.quant']._update_available_quantity(product, stock_location, -1.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(product, stock_location), available_quantity - 1) - self.assertEqual(len(self.env['stock.quant']._gather(product, stock_location, strict=True)), 2) + self.assertEqual(len(self.gather_relevant(product, stock_location, strict=True)), 2) def test_decrease_available_quantity_4(self): """ Decrease the available quantity that delete the quant. The active user should have @@ -363,7 +367,7 @@ class StockQuant(TransactionCase): }) self.env = self.env(user=self.demo_user) self.env['stock.quant']._update_available_quantity(product1, stock_location, -1.0) - self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 0) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 0) def test_increase_reserved_quantity_1(self): """ Increase the reserved quantity of quantity x when there's a single quant in a given @@ -380,10 +384,10 @@ class StockQuant(TransactionCase): 'quantity': 10.0, }) 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) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 1) self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 10.0) 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.assertEqual(len(self.gather_relevant(product1, stock_location)), 1) def test_increase_reserved_quantity_2(self): """ Increase the reserved quantity of quantity x when there's two quants in a given @@ -401,10 +405,10 @@ class StockQuant(TransactionCase): 'quantity': 5.0, }) 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)), 2) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 2) self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 10.0) 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)), 2) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 2) def test_increase_reserved_quantity_3(self): """ Increase the reserved quantity of quantity x when there's multiple quants in a given @@ -442,10 +446,10 @@ class StockQuant(TransactionCase): # total quantity: 58 # total reserved quantity: 29 self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 29.0) - self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 4) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 4) self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 10.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 19.0) - self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 4) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 4) def test_increase_reserved_quantity_4(self): """ Increase the reserved quantity of quantity x when there's multiple quants in a given @@ -469,7 +473,7 @@ class StockQuant(TransactionCase): 'reserved_quantity': 10.0, }) 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)), 2) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 2) with self.assertRaises(UserError): self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 10.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0) @@ -499,10 +503,10 @@ class StockQuant(TransactionCase): 'reserved_quantity': 10.0, }) 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.assertEqual(len(self.gather_relevant(product1, stock_location)), 1) 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) + self.assertEqual(len(self.gather_relevant(product1, stock_location)), 1) def test_increase_decrease_reserved_quantity_1(self): """ Decrease then increase reserved quantity when no quant are in a location. diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index ef6c72cbf27..592d0138515 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -283,16 +283,16 @@ class TestStockFlow(TestStockCommon): self.assertEqual(sum(total_qty), 2.0, 'Expecting 2.0 Unit , got %.4f Unit on location stock!' % (sum(total_qty))) self.assertEqual(self.productA.qty_available, 2.0, 'Wrong quantity available (%s found instead of 2.0)' % (self.productA.qty_available)) # Check quants and available quantity for product B - quants = self.StockQuantObj.search([('product_id', '=', self.productB.id), ('location_id', '=', self.stock_location)]) + quants = self.StockQuantObj.search([('product_id', '=', self.productB.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)]) self.assertFalse(quants, 'No quant should found as outgoing shipment took everything out of stock.') self.assertEqual(self.productB.qty_available, 0.0, 'Product B should have zero quantity available.') # Check quants and available quantity for product C - quants = self.StockQuantObj.search([('product_id', '=', self.productC.id), ('location_id', '=', self.stock_location)]) + quants = self.StockQuantObj.search([('product_id', '=', self.productC.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)]) total_qty = [quant.quantity for quant in quants] self.assertEqual(sum(total_qty), 2.0, 'Expecting 2.0 Unit, got %.4f Unit on location stock!' % (sum(total_qty))) self.assertEqual(self.productC.qty_available, 2.0, 'Wrong quantity available (%s found instead of 2.0)' % (self.productC.qty_available)) # Check quants and available quantity for product D - quant = self.StockQuantObj.search([('product_id', '=', self.productD.id), ('location_id', '=', self.stock_location)], limit=1) + quant = self.StockQuantObj.search([('product_id', '=', self.productD.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)], limit=1) self.assertEqual(quant.quantity, 1.0, 'Expecting 1.0 Unit , got %.4f Unit on location stock!' % (quant.quantity)) self.assertEqual(self.productD.qty_available, 1.0, 'Wrong quantity available (%s found instead of 1.0)' % (self.productD.qty_available)) @@ -395,21 +395,21 @@ class TestStockFlow(TestStockCommon): # ----------------------------------------------------------------------- # Check quants and available quantity for product A. - quants = self.StockQuantObj.search([('product_id', '=', self.productA.id), ('location_id', '=', self.stock_location)]) + quants = self.StockQuantObj.search([('product_id', '=', self.productA.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)]) total_qty = [quant.quantity for quant in quants] self.assertEqual(sum(total_qty), 12.0, 'Wrong total stock location quantity (%s found instead of 12)' % (sum(total_qty))) self.assertEqual(self.productA.qty_available, 12.0, 'Wrong quantity available (%s found instead of 12)' % (self.productA.qty_available)) # Check quants and available quantity for product B. - quants = self.StockQuantObj.search([('product_id', '=', self.productB.id), ('location_id', '=', self.stock_location)]) + quants = self.StockQuantObj.search([('product_id', '=', self.productB.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)]) self.assertFalse(quants, 'No quant should found as outgoing shipment took everything out of stock') self.assertEqual(self.productB.qty_available, 0.0, 'Total quantity in stock should be 0 as the backorder took everything out of stock') # Check quants and available quantity for product C. - quants = self.StockQuantObj.search([('product_id', '=', self.productC.id), ('location_id', '=', self.stock_location)]) + quants = self.StockQuantObj.search([('product_id', '=', self.productC.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)]) total_qty = [quant.quantity for quant in quants] self.assertEqual(sum(total_qty), 8.0, 'Wrong total stock location quantity (%s found instead of 8)' % (sum(total_qty))) self.assertEqual(self.productC.qty_available, 8.0, 'Wrong quantity available (%s found instead of 8)' % (self.productC.qty_available)) # Check quants and available quantity for product D. - quants = self.StockQuantObj.search([('product_id', '=', self.productD.id), ('location_id', '=', self.stock_location)]) + quants = self.StockQuantObj.search([('product_id', '=', self.productD.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)]) total_qty = [quant.quantity for quant in quants] self.assertEqual(sum(total_qty), 9.0, 'Wrong total stock location quantity (%s found instead of 9)' % (sum(total_qty))) self.assertEqual(self.productD.qty_available, 9.0, 'Wrong quantity available (%s found instead of 9)' % (self.productD.qty_available)) @@ -421,7 +421,7 @@ class TestStockFlow(TestStockCommon): back_order_out.action_done() # Check stock location quants and available quantity for product A. - quants = self.StockQuantObj.search([('product_id', '=', self.productA.id), ('location_id', '=', self.stock_location)]) + quants = self.StockQuantObj.search([('product_id', '=', self.productA.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)]) total_qty = [quant.quantity for quant in quants] self.assertGreaterEqual(float_round(sum(total_qty), precision_rounding=0.0001), 1, 'Total stock location quantity for product A should not be nagative.') @@ -1549,7 +1549,7 @@ class TestStockFlow(TestStockCommon): self.assertEqual(neg_quants.location_id.id, self.supplier_location, 'There shoud be 1 negative quants for supplier!') # We should also make sure that when matching stock moves with pack operations, it takes the correct quants = self.env['stock.quant']._gather(self.productE, self.env['stock.location'].browse(self.stock_location)) - self.assertEqual(len(quants), 0, 'We should have no quants in the end') + self.assertEqual(sum(quants.mapped('quantity')), 0, 'We should have no quants in the end') def test_70_picking_state_all_at_once_reserve(self): """ This test will check that the state of the picking is correctly computed according diff --git a/addons/stock/tests/test_warehouse.py b/addons/stock/tests/test_warehouse.py index 88ff79b7c7d..f0f260889d3 100644 --- a/addons/stock/tests/test_warehouse.py +++ b/addons/stock/tests/test_warehouse.py @@ -169,7 +169,7 @@ class TestWarehouse(TestStockCommon): return_pick.action_done() quant = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', stock_location.id)]) - self.assertEqual(len(quant), 0) + self.assertEqual(sum(quant.mapped('quantity')), 0) def test_inventory_adjustment_and_negative_quants_2(self): """Make sure negative quants get wiped out with an inventory adjustment""" @@ -220,7 +220,7 @@ class TestWarehouse(TestStockCommon): # There should be no quant in the stock location quants = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', stock_location.id)]) - self.assertEqual(len(quants), 0) + self.assertEqual(sum(quants.mapped('quantity')), 0) # There should be one quant in the inventory loss location quant = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', location_loss.id)]) @@ -385,7 +385,7 @@ class TestWarehouse(TestStockCommon): # Check that the correct quantity has been provided to customer self.assertEqual(self.env['stock.quant']._gather(product, customer_location).quantity, 1) # Ensure there still no quants in distribution warehouse - self.assertEqual(len(self.env['stock.quant']._gather(product, warehouse_distribution_namur.lot_stock_id)), 0) + self.assertEqual(sum(self.env['stock.quant']._gather(product, warehouse_distribution_namur.lot_stock_id).mapped('quantity')), 0) # Create the move for the shop Wavre. Should create a resupply from # distribution warehouse Wavre. @@ -429,7 +429,7 @@ class TestWarehouse(TestStockCommon): # Check that the correct quantity has been provided to customer self.assertEqual(self.env['stock.quant']._gather(product, customer_location).quantity, 2) # Ensure there still no quants in distribution warehouse - self.assertEqual(len(self.env['stock.quant']._gather(product, warehouse_distribution_wavre.lot_stock_id)), 0) + self.assertEqual(sum(self.env['stock.quant']._gather(product, warehouse_distribution_wavre.lot_stock_id).mapped('quantity')), 0) class TestResupply(TestStockCommon): def setUp(self): diff --git a/addons/stock/tests/test_wise_operator.py b/addons/stock/tests/test_wise_operator.py index 2bfd8c08785..79e638b2768 100644 --- a/addons/stock/tests/test_wise_operator.py +++ b/addons/stock/tests/test_wise_operator.py @@ -171,5 +171,6 @@ class TestWiseOperator(TransactionCase): records = self.env['stock.quant'].search([ ('product_id', '=', product_wise.id), ('location_id', '!=', self.ref('stock.stock_location_suppliers'))]) - self.assertTrue(all([x.location_id.id == self.ref('stock.stock_location_customers') and x.quantity > 0.0 for x in records]), + self.assertTrue(all([x.location_id.id == self.ref('stock.stock_location_customers') and x.quantity > 0.0 or + x.location_id.id != self.ref('stock.stock_location_customers') and x.quantity == 0.0 for x in records]), "Negative quant or wrong location detected") diff --git a/addons/stock/wizard/stock_quantity_history.py b/addons/stock/wizard/stock_quantity_history.py index e138bce9bb3..595a5c8de88 100644 --- a/addons/stock/wizard/stock_quantity_history.py +++ b/addons/stock/wizard/stock_quantity_history.py @@ -33,4 +33,5 @@ class StockQuantityHistory(models.TransientModel): return action else: self.env['stock.quant']._merge_quants() + self.env['stock.quant']._unlink_zero_quants() return self.env.ref('stock.quantsact').read()[0] diff --git a/addons/stock_picking_batch/tests/test_batch_picking.py b/addons/stock_picking_batch/tests/test_batch_picking.py index 6a8a1e9d393..c3e2f8dbcde 100644 --- a/addons/stock_picking_batch/tests/test_batch_picking.py +++ b/addons/stock_picking_batch/tests/test_batch_picking.py @@ -83,8 +83,8 @@ class TestBatchPicking(TransactionCase): quant_B = self.env['stock.quant']._gather(self.productB, self.stock_location) # ensure that quantity for picking has been moved - self.assertFalse(quant_A) - self.assertFalse(quant_B) + self.assertFalse(sum(quant_A.mapped('quantity'))) + self.assertFalse(sum(quant_B.mapped('quantity'))) def test_simple_batch_with_wizard(self): """ Test a simple batch picking with all quantity for picking available. @@ -113,8 +113,8 @@ class TestBatchPicking(TransactionCase): quant_B = self.env['stock.quant']._gather(self.productB, self.stock_location) # ensure that quantity for picking has been moved - self.assertFalse(quant_A) - self.assertFalse(quant_B) + self.assertFalse(sum(quant_A.mapped('quantity'))) + self.assertFalse(sum(quant_B.mapped('quantity'))) def test_batch_with_backorder_wizard(self): """ Test a simple batch picking with only one quantity fully available. @@ -148,8 +148,8 @@ class TestBatchPicking(TransactionCase): quant_B = self.env['stock.quant']._gather(self.productB, self.stock_location) # ensure that quantity for picking has been moved - self.assertFalse(quant_A) - self.assertFalse(quant_B) + self.assertFalse(sum(quant_A.mapped('quantity'))) + self.assertFalse(sum(quant_B.mapped('quantity'))) def test_batch_with_immediate_transfer_and_backorder_wizard(self): """ Test a simple batch picking with only one product fully available. @@ -184,8 +184,8 @@ class TestBatchPicking(TransactionCase): quant_B = self.env['stock.quant']._gather(self.productB, self.stock_location) # ensure that quantity for picking has been moved - self.assertFalse(quant_A) - self.assertFalse(quant_B) + self.assertFalse(sum(quant_A.mapped('quantity'))) + self.assertFalse(sum(quant_B.mapped('quantity'))) def test_batch_with_immediate_transfer_and_backorder_wizard_with_manual_operations(self): """ Test a simple batch picking with only one quantity fully available. @@ -221,5 +221,5 @@ class TestBatchPicking(TransactionCase): quant_B = self.env['stock.quant']._gather(self.productB, self.stock_location) # ensure that quantity for picking has been moved - self.assertFalse(quant_A) - self.assertFalse(quant_B) \ No newline at end of file + self.assertFalse(sum(quant_A.mapped('quantity'))) + self.assertFalse(sum(quant_B.mapped('quantity')))