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')))