diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index fef75714961..632983fab30 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -583,3 +583,23 @@ class ProductCategory(models.Model): category = category.parent_id routes |= category.route_ids self.total_route_ids = routes + + +class ProductUoM(models.Model): + _inherit = 'product.uom' + + def write(self, values): + # Users can not update the factor if open stock moves are based on it + if 'factor' in values or 'factor_inv' in values or 'category_id' in values: + stock_move_lines = self.env['stock.move.line'].search_count([ + ('product_uom_id.category_id', '=', self.category_id.id), + ('state', '!=', 'cancel'), + ]) + + if stock_move_lines: + raise UserError(_( + "You cannot change the ratio of this unit of mesure as some" + " products with this UoM have already been moved or are " + "currently reserved." + )) + return super(ProductUoM, self).write(values) diff --git a/addons/stock/tests/__init__.py b/addons/stock/tests/__init__.py index a9a18e270e4..f0272143725 100644 --- a/addons/stock/tests/__init__.py +++ b/addons/stock/tests/__init__.py @@ -9,3 +9,4 @@ from . import test_quant from . import test_inventory from . import test_move from . import test_move2 +from . import test_robustness diff --git a/addons/stock/tests/test_robustness.py b/addons/stock/tests/test_robustness.py new file mode 100644 index 00000000000..4e49a4c9bc1 --- /dev/null +++ b/addons/stock/tests/test_robustness.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.exceptions import UserError +from odoo.tests.common import TransactionCase + + +class TestRobustness(TransactionCase): + def setUp(self): + super(TestRobustness, self).setUp() + self.stock_location = self.env.ref('stock.stock_location_stock') + self.customer_location = self.env.ref('stock.stock_location_customers') + self.uom_unit = self.env.ref('product.product_uom_unit') + self.uom_dozen = self.env.ref('product.product_uom_dozen') + self.product1 = self.env['product.product'].create({ + 'name': 'Product A', + 'type': 'product', + 'categ_id': self.env.ref('product.product_category_all').id, + }) + + def test_uom_factor(self): + """ Changing the factor of a unit of measure shouldn't be allowed while + quantities are reserved, else the existing move lines won't be consistent + with the `reserved_quantity` on quants. + """ + # make some stock + self.env['stock.quant']._update_available_quantity( + self.product1, + self.stock_location, + 12, + ) + + # reserve a dozen + move1 = self.env['stock.move'].create({ + 'name': 'test_uom_rounding', + 'location_id': self.stock_location.id, + 'location_dest_id': self.customer_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_dozen.id, + 'product_uom_qty': 1, + }) + move1._action_confirm() + move1._action_assign() + self.assertEqual(move1.state, 'assigned') + quant = self.env['stock.quant']._gather( + self.product1, + self.stock_location, + ) + + # assert the reservation + self.assertEqual(quant.reserved_quantity, 12) + self.assertEqual(move1.product_qty, 12) + + # change the factor + with self.assertRaises(UserError): + with self.cr.savepoint(): + move1.product_uom.factor = 0.05 + + # assert the reservation + self.assertEqual(quant.reserved_quantity, 12) + self.assertEqual(move1.state, 'assigned') + self.assertEqual(move1.product_qty, 12) + + # unreserve + move1._do_unreserve()