From 7d864341dd99aa7c15173178cebaec09e64f359a Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Sat, 6 Jul 2019 17:28:05 +0200 Subject: [PATCH] Migrate stock_available_mrp to v12 --- stock_available_mrp/__manifest__.py | 2 +- stock_available_mrp/demo/mrp_data.xml | 24 ++--- stock_available_mrp/models/product_product.py | 2 +- stock_available_mrp/readme/CONTRIBUTORS.rst | 5 + stock_available_mrp/readme/DESCRIPTION.rst | 6 ++ stock_available_mrp/readme/ROADMAP.rst | 24 +++++ .../tests/test_potential_qty.py | 96 ++++++++++--------- 7 files changed, 101 insertions(+), 58 deletions(-) create mode 100644 stock_available_mrp/readme/CONTRIBUTORS.rst create mode 100644 stock_available_mrp/readme/DESCRIPTION.rst create mode 100644 stock_available_mrp/readme/ROADMAP.rst diff --git a/stock_available_mrp/__manifest__.py b/stock_available_mrp/__manifest__.py index 58a507f11..2e3fe43f2 100644 --- a/stock_available_mrp/__manifest__.py +++ b/stock_available_mrp/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '11.0.1.0.0', + 'version': '12.0.1.0.0', "author": "Numérigraphe," "Odoo Community Association (OCA)", 'website': 'https://github.com/OCA/stock-logistics-warehouse', diff --git a/stock_available_mrp/demo/mrp_data.xml b/stock_available_mrp/demo/mrp_data.xml index a41a04f4c..1dc82fee9 100644 --- a/stock_available_mrp/demo/mrp_data.xml +++ b/stock_available_mrp/demo/mrp_data.xml @@ -3,11 +3,11 @@ PCSC234-WHITE + ref="mrp.product_product_table_kit_product_template"/> - + @@ -16,21 +16,21 @@ - - Apple Wireless Keyboard + + Bolt - 10.0 - 47.0 + 1.0 + 5.0 product - - - E-COM10-WHITE + + + BOLT-WHITE - - 1 - + + 4 + 5 diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 8798c3ffe..0cafb61e8 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -13,7 +13,7 @@ class ProductProduct(models.Model): bom_id = fields.Many2one( 'mrp.bom', compute='_compute_bom_id', - string='Bill of Materials' + string='BOM' ) @api.depends('virtual_available', 'bom_id', 'bom_id.product_qty') diff --git a/stock_available_mrp/readme/CONTRIBUTORS.rst b/stock_available_mrp/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..e274f35bc --- /dev/null +++ b/stock_available_mrp/readme/CONTRIBUTORS.rst @@ -0,0 +1,5 @@ +* Loïc Bellier (Numérigraphe) +* Lionel Sausin (Numérigraphe) +* many thanks to Graeme Gellatly for his advice and code review +* Laurent Mignon +* Cédric Pigeon diff --git a/stock_available_mrp/readme/DESCRIPTION.rst b/stock_available_mrp/readme/DESCRIPTION.rst new file mode 100644 index 000000000..83df836a5 --- /dev/null +++ b/stock_available_mrp/readme/DESCRIPTION.rst @@ -0,0 +1,6 @@ +This module takes the potential quantities available for Products into account in +the quantity available to promise, where the "Potential quantity" is the +quantity that can be manufactured with the components immediately at hand. +By configuration, the "Potential quantity" can be computed based on other product field. +For example, "Potential quantity" can be the quantity that can be manufactured +with the components available to promise. diff --git a/stock_available_mrp/readme/ROADMAP.rst b/stock_available_mrp/readme/ROADMAP.rst new file mode 100644 index 000000000..7283e16c0 --- /dev/null +++ b/stock_available_mrp/readme/ROADMAP.rst @@ -0,0 +1,24 @@ +Known issues +------------ +The manufacturing delays are not taken into account : this module assumes that +if you have components in stock goods, you can manufacture finished goods +quickly enough. + +As a consequence, and to avoid overestimating, **only the first level** of Bill of Materials is +considered. +However Sets (a.k.a "phantom" BoMs) are taken into account: if a component must be replaced with a set, it's the stock of the set's product which will decide the potential. + +If a product has several variants, only the variant with the biggest potential will be taken into account when reporting the production potential. +For example, even if you actually have enough components to make 10 iPads 16Go AND 42 iPads 32Go, we'll consider that you can promise only 42 iPads. + +Removed features +---------------- +Previous versions of this module used to let programmers demand to get the potential quantity in an arbitrary Unit of Measure using the `context`. This feature was present in the standard computations too until v8.0, but it has been dropped from the standard from v8.0 on. +For the sake of consistency the potential quantity is now always reported in the product's main Unit of Measure too. + +Roadmap +------- +Possible improvements for future versions: +* take manufacturing delays into account: we should not promise goods to customers if they want them delivered earlier that we can make them +* Compute the quantity of finished product that can be made directly on each Bill of Material: this would be useful for production managers, and may make the computations faster by avoiding to compute the same BoM several times when several variants share the same BoM +* add an option (probably as a sub-module) to consider all raw materials as available if they can be bought from the suppliers in time for the manufacturing. diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index cdd16916f..363ed7a44 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -29,20 +29,28 @@ class TestPotentialQty(TransactionCase): def setup_demo_data(self): #  An interesting product (multi-line BoM, variants) self.tmpl = self.browse_ref( - 'mrp.product_product_build_kit_product_template') + 'mrp.product_product_table_kit_product_template') #  First variant - self.var1 = self.browse_ref('mrp.product_product_build_kit') + self.var1 = self.browse_ref('mrp.product_product_table_kit') self.var1.type = 'product' #  Second variant self.var2 = self.browse_ref( 'stock_available_mrp.product_kit_1a') self.var2.type = 'product' + # Make bolt a stockable product to be able to change its stock + # we need to unreserve the existing move before being able to do it. + bolt = self.env.ref('mrp.product_product_computer_desk_bolt') + bolt_moves = self.env['stock.move'].search( + [('product_id', '=', bolt.id), + ('state', 'not in', ('done', 'cancel'))]) + bolt_moves._do_unreserve() + bolt.type = 'product' # Components that can be used to make the product component_ids = [ - # KeyBoard - self.ref('product.product_product_9'), - # Mouse - self.ref('product.product_product_12'), + # Bolt + bolt.id, + # Wood Panel + self.ref('mrp.product_product_wood_panel'), ] # Zero-out the inventory of all variants and components @@ -81,7 +89,7 @@ class TestPotentialQty(TransactionCase): 'location_id': location_id, 'product_qty': qty }) - inventory.action_done() + inventory._action_done() def create_simple_bom(self, product, sub_product, product_qty=1, sub_product_qty=1, @@ -118,7 +126,7 @@ class TestPotentialQty(TransactionCase): def test_potential_qty_no_bom_for_company(self): chicago_id = self.ref('stock.res_company_1') - # Receive 1000x CPUI5s owned by Chicago + # Receive 1000x Wood Panel owned by Chicago inventory = self.env['stock.inventory'].create( {'name': 'Receive CPUa8', 'company_id': chicago_id, @@ -128,12 +136,12 @@ class TestPotentialQty(TransactionCase): self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'company_id': chicago_id, - 'product_id': self.ref('product.product_product_9'), + 'product_id': self.ref('mrp.product_product_wood_panel'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) - inventory.action_done() + inventory._action_done() - # Put RAM-SR5 owned by Chicago for 1000x the 1st variant in main WH + # Put Bolt owned by Chicago for 1000x the 1st variant in main WH inventory = self.env['stock.inventory'].create( {'name': 'components for 1st variant', 'company_id': chicago_id, @@ -143,12 +151,12 @@ class TestPotentialQty(TransactionCase): self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'company_id': chicago_id, - 'product_id': self.ref('product.product_product_12'), + 'product_id': self.ref('mrp.product_product_computer_desk_bolt'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) - inventory.action_done() + inventory._action_done() self.assertPotentialQty( - self.tmpl, 1000.0, + self.tmpl, 250.0, "Wrong template potential after receiving components") test_user = self.env['res.users'].create( @@ -164,7 +172,7 @@ class TestPotentialQty(TransactionCase): test_user_tmpl = self.tmpl.sudo(test_user) self.assertPotentialQty( - test_user_tmpl, 1000.0, + test_user_tmpl, 250.0, "Simple user can access to the potential_qty") # Set the bom on the main company (visible to members of main company) @@ -181,7 +189,7 @@ class TestPotentialQty(TransactionCase): "company or company child of the bom's company") bom.company_id = chicago_id self.assertPotentialQty( - test_user_tmpl, 1000.0, '') + test_user_tmpl, 250.0, '') def test_potential_qty(self): for i in [self.tmpl, self.var1, self.var2]: @@ -189,7 +197,7 @@ class TestPotentialQty(TransactionCase): i, 0.0, "The potential quantity should start at 0") - # Receive 1000x Mouses + # Receive 1000x Wood Panel inventory = self.env['stock.inventory'].create( {'name': 'Receive Mouses', 'location_id': self.wh_main.lot_stock_id.id, @@ -197,17 +205,17 @@ class TestPotentialQty(TransactionCase): inventory.action_start() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_12'), + 'product_id': self.ref('mrp.product_product_wood_panel'), 'location_id': self.wh_main.lot_stock_id.id, 'product_qty': 1000.0}) - inventory.action_done() + inventory._action_done() for i in [self.tmpl, self.var1, self.var2]: self.assertPotentialQty( i, 0.0, "Receiving a single component should not change the " "potential of %s" % i) - # Receive enough keyboard to make 1000x the 1st variant in main WH + # Receive enough bolt to make 1000x the 1st variant in main WH inventory = self.env['stock.inventory'].create( {'name': 'components for 1st variant', 'location_id': self.wh_main.lot_stock_id.id, @@ -216,22 +224,22 @@ class TestPotentialQty(TransactionCase): self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'product_id': self.ref( - 'product.product_product_9'), + 'mrp.product_product_computer_desk_bolt'), 'location_id': self.wh_main.lot_stock_id.id, 'product_qty': 1000.0}) - inventory.action_done() + inventory._action_done() self.assertPotentialQty( - self.tmpl, 1000.0, + self.tmpl, 250.0, "Wrong template potential after receiving components") self.assertPotentialQty( - self.var1, 1000.0, + self.var1, 250.0, "Wrong variant 1 potential after receiving components") self.assertPotentialQty( self.var2, 0.0, "Receiving variant 1's component should not change " "variant 2's potential") - # Receive enough components to make 313 the 2nd variant at Chicago + # Receive enough components to make 213 the 2nd variant at Chicago inventory = self.env['stock.inventory'].create( {'name': 'components for 2nd variant', 'location_id': self.wh_ch.lot_stock_id.id, @@ -239,64 +247,64 @@ class TestPotentialQty(TransactionCase): inventory.action_start() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_12'), + 'product_id': self.ref('mrp.product_product_wood_panel'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'product_id': self.ref( - 'stock_available_mrp.product_product_9_white'), + 'stock_available_mrp.product_computer_desk_bolt_white'), 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 313.0}) - inventory.action_done() + 'product_qty': 852.0}) + inventory._action_done() self.assertPotentialQty( - self.tmpl, 1000.0, + self.tmpl.with_context(test=True), 250.0, "Wrong template potential after receiving components") self.assertPotentialQty( - self.var1, 1000.0, + self.var1, 250.0, "Receiving variant 2's component should not change " "variant 1's potential") self.assertPotentialQty( - self.var2, 313.0, + self.var2, 213.0, "Wrong variant 2 potential after receiving components") # Check by warehouse self.assertPotentialQty( - self.tmpl.with_context(warehouse=self.wh_main.id), 1000.0, + self.tmpl.with_context(warehouse=self.wh_main.id), 250.0, "Wrong potential quantity in main WH") self.assertPotentialQty( - self.tmpl.with_context(warehouse=self.wh_ch.id), 313.0, + self.tmpl.with_context(warehouse=self.wh_ch.id), 213.0, "Wrong potential quantity in Chicago WH") # Check by location self.assertPotentialQty( self.tmpl.with_context( - location=self.wh_main.lot_stock_id.id), 1000.0, + location=self.wh_main.lot_stock_id.id), 250.0, "Wrong potential quantity in main WH location") self.assertPotentialQty( self.tmpl.with_context( location=self.wh_ch.lot_stock_id.id), - 313.0, + 213.0, "Wrong potential quantity in Chicago WH location") def test_multi_unit_recursive_bom(self): # Test multi-level and multi-units BOM - uom_unit = self.env.ref('product.product_uom_unit') + uom_unit = self.env.ref('uom.product_uom_unit') uom_unit.rounding = 1.0 p1 = self.product_model.create({ 'name': 'Test product with BOM', 'type': 'product', - 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_id': self.env.ref('uom.product_uom_unit').id, }) p2 = self.product_model.create({ 'name': 'Test sub product with BOM', 'type': 'product', - 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_id': self.env.ref('uom.product_uom_unit').id, }) p3 = self.product_model.create({ 'name': 'Test component', 'type': 'product', - 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_id': self.env.ref('uom.product_uom_unit').id, }) bom_p1 = self.bom_model.create({ @@ -308,7 +316,7 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p1.id, 'product_id': p3.id, 'product_qty': 1, - 'product_uom_id': self.env.ref('product.product_uom_unit').id, + 'product_uom_id': self.env.ref('uom.product_uom_unit').id, }) @@ -317,7 +325,7 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p1.id, 'product_id': p2.id, 'product_qty': 2, - 'product_uom_id': self.env.ref('product.product_uom_unit').id, + 'product_uom_id': self.env.ref('uom.product_uom_unit').id, }) @@ -332,7 +340,7 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p2.id, 'product_id': p3.id, 'product_qty': 2, - 'product_uom_id': self.env.ref('product.product_uom_unit').id, + 'product_uom_id': self.env.ref('uom.product_uom_unit').id, }) @@ -365,7 +373,7 @@ class TestPotentialQty(TransactionCase): # Test to change component stock for compute BOM stock # Get a demo product with outgoing move (qty: 3) - prod = self.browse_ref('product.product_product_20') + prod = self.browse_ref('product.product_product_16') # Set on hand qty self.create_inventory(prod.id, 3)