From e23de10ffd3a652a44adeb7723ebb76c2b425305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Pigeon?= Date: Tue, 9 May 2017 21:26:20 +0200 Subject: [PATCH] [10.0][MIG] port stock_available_mrp to 10.0 --- stock_available_mrp/README.rst | 1 + .../{__openerp__.py => __manifest__.py} | 6 +- stock_available_mrp/demo/mrp_bom.yml | 27 --- stock_available_mrp/demo/mrp_data.xml | 38 +++ stock_available_mrp/models/product_product.py | 217 +++++++++--------- .../models/product_template.py | 44 +--- .../tests/test_potential_qty.py | 184 ++++----------- .../views/product_template_view.xml | 6 +- 8 files changed, 209 insertions(+), 314 deletions(-) rename stock_available_mrp/{__openerp__.py => __manifest__.py} (85%) delete mode 100644 stock_available_mrp/demo/mrp_bom.yml create mode 100644 stock_available_mrp/demo/mrp_data.xml diff --git a/stock_available_mrp/README.rst b/stock_available_mrp/README.rst index b47d8dbf0..d0e8733f5 100644 --- a/stock_available_mrp/README.rst +++ b/stock_available_mrp/README.rst @@ -69,6 +69,7 @@ Contributors * Lionel Sausin (Numérigraphe) * many thanks to Graeme Gellatly for his advice and code review * Laurent Mignon +* Cédric Pigeon Maintainer ---------- diff --git a/stock_available_mrp/__openerp__.py b/stock_available_mrp/__manifest__.py similarity index 85% rename from stock_available_mrp/__openerp__.py rename to stock_available_mrp/__manifest__.py index da88f11bd..b70a3108b 100644 --- a/stock_available_mrp/__openerp__.py +++ b/stock_available_mrp/__manifest__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '9.0.1.0.0', + 'version': '10.0.1.0.0', "author": u"Numérigraphe," u"Odoo Community Association (OCA)", 'category': 'Hidden', @@ -15,8 +15,8 @@ 'views/product_template_view.xml', ], 'demo': [ - 'demo/mrp_bom.yml', + 'demo/mrp_data.xml', ], 'license': 'AGPL-3', - 'installable': False, + 'installable': True, } diff --git a/stock_available_mrp/demo/mrp_bom.yml b/stock_available_mrp/demo/mrp_bom.yml deleted file mode 100644 index bb197f44b..000000000 --- a/stock_available_mrp/demo/mrp_bom.yml +++ /dev/null @@ -1,27 +0,0 @@ -- Create a UoM in the category of PCE -- !record {model: product.uom, id: thousand}: - name: Thousand - factor: 0.001 - rounding: 0.001 - uom_type: bigger - category_id: product.product_uom_categ_unit - -- Add a BOM whereby 0.042K "RAM SR2" can be replaced with 13 dozens "HDD-SH1" + 8 CPUa8 with 50% efficiency. This lets us test UoM conversions for the finished product and the raw materials, as well as the unfolding of phantom BoMs -- !record {model: mrp.bom, id: sr2_from_hdd}: - product_id: product.product_product_14 - product_tmpl_id: product.product_product_14_product_template - product_uom: thousand - product_qty: 0.042 - type: phantom - sequence: -1 - product_efficiency: 0.5 -- !record {model: mrp.bom.line, id: sr2_from_hdd_line1}: - bom_id: sr2_from_hdd - product_id: product.product_product_18 - product_qty: 13 - product_uom: product.product_uom_dozen -- !record {model: mrp.bom.line, id: sr2_from_hdd_line2}: - bom_id: sr2_from_hdd - product_id: product.product_product_23 - product_qty: 8 - product_uom: product.product_uom_unit diff --git a/stock_available_mrp/demo/mrp_data.xml b/stock_available_mrp/demo/mrp_data.xml new file mode 100644 index 000000000..0099e9349 --- /dev/null +++ b/stock_available_mrp/demo/mrp_data.xml @@ -0,0 +1,38 @@ + + + + PCSC234-WHITE + + + + + + + + + + + + + + Apple Wireless Keyboard + + 10.0 + 47.0 + consu + + + E-COM10-WHITE + + + + + 1 + + 5 + + + + diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 790ca16a4..9417b3052 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -2,133 +2,140 @@ # © 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from collections import Counter - -from openerp import models, fields, api -from openerp.addons import decimal_precision as dp - -from openerp.exceptions import AccessError +from collections import Counter, defaultdict +from odoo import api, fields, models class ProductProduct(models.Model): + _inherit = 'product.product' - potential_qty = fields.Float( - compute='_get_potential_qty', - type='float', - digits_compute=dp.get_precision('Product Unit of Measure'), - string='Potential', - help="Quantity of this Product that could be produced using " - "the materials already at hand.") - - # Needed for fields dependencies - # When self.potential_qty is compute, we want to force the ORM - # to compute all the components potential_qty too. - component_ids = fields.Many2many( - comodel_name='product.product', - compute='_get_component_ids', + bom_id = fields.Many2one( + 'mrp.bom', + compute='_compute_bom_id', + string='Bill of Materials' ) - @api.multi - @api.depends('potential_qty') - def _immediately_usable_qty(self): - """Add the potential quantity to the quantity available to promise. - - This is the same implementation as for templates.""" - super(ProductProduct, self)._immediately_usable_qty() - for product in self: - product.immediately_usable_qty += product.potential_qty + @api.depends('virtual_available') + def _compute_available_quantities(self): + super(ProductProduct, self)._compute_available_quantities() @api.multi - @api.depends('component_ids.potential_qty') - def _get_potential_qty(self): - """Compute the potential qty based on the available components.""" - bom_obj = self.env['mrp.bom'] - uom_obj = self.env['product.uom'] - + def _compute_bom_id(self): + domain = [('product_id', 'in', self.ids)] + product_tmpl_ids = [] + bom_product_ids = self.env['mrp.bom'].search(domain) + # find bom linked to a product + bom_by_product_id = { + b.product_id.id: b for b in bom_product_ids} + product_id_found = bom_by_product_id.keys() for product in self: - bom_id = bom_obj._bom_find(product_id=product.id) - if not bom_id: - product.potential_qty = 0.0 - continue + if product.id not in product_id_found: + product_tmpl_ids.append(product.product_tmpl_id.id) + domain = [('product_id', '=', False), + ('product_tmpl_id', 'in', product_tmpl_ids)] + # find boms linked to the product template + bom_product_template = self.env['mrp.bom'].search(domain) + bom_by_product_tmpl_id = { + b.product_tmpl_id.id: b for b in bom_product_template} + for product in self: + product.bom_id = bom_by_product_id.get( + product.id, + bom_by_product_tmpl_id.get(product.product_tmpl_id.id) + ) - bom = bom_obj.browse(bom_id) + @api.multi + def _compute_available_quantities_dict(self): + res = super(ProductProduct, self)._compute_available_quantities_dict() - # Need by product (same product can be in many BOM lines/levels) - try: - component_needs = self._get_components_needs(product, bom) - except AccessError: - # If user doesn't have access to BOM - # he can't see potential_qty - component_needs = None + # compute qty for product with bom + product_with_bom = self.filtered(lambda p: p.bom_id) - if not component_needs: - # The BoM has no line we can use - product.potential_qty = 0.0 - - else: - # Find the lowest quantity we can make with the stock at hand - components_potential_qty = min( - [self._get_component_qty(component) // need - for component, need in component_needs.items()] - ) - - # Compute with bom quantity - bom_qty = uom_obj._compute_qty_obj( - bom.product_uom, - bom.product_qty, - bom.product_tmpl_id.uom_id - ) - product.potential_qty = bom_qty * components_potential_qty - - def _get_component_qty(self, component): - """ Return the component qty to use based en company settings. - - :type component: product_product - :rtype: float - """ + if not product_with_bom: + return res icp = self.env['ir.config_parameter'] stock_available_mrp_based_on = icp.get_param( 'stock_available_mrp_based_on', 'qty_available' ) - return component[stock_available_mrp_based_on] + # from here we start the computation of bom qties in an isolated + # environment to avoid trouble with prefetch and cache + product_with_bom = product_with_bom.with_context( + unique=models.NewId()).with_prefetch(defaultdict(set)) - def _get_components_needs(self, product, bom): - """ Return the needed qty of each compoments in the *bom* of *product*. + # explode all boms at once + exploded_boms = product_with_bom._explode_boms() - :type product: product_product - :type bom: mrp_bom + # extract the list of product used as bom component + product_components_ids = set() + for exploded_components in exploded_boms.values(): + for bom_component in exploded_components: + product_components_ids.add(bom_component[0].product_id.id) + + # Compute stock for product components. + # {'productid': {field_name: qty}} + component_products = product_with_bom.browse( + product_components_ids) + if stock_available_mrp_based_on in res.keys(): + # If the qty is computed by the same method use it to avoid + # stressing the cache + component_qties = \ + component_products._compute_available_quantities_dict() + else: + # The qty is a field computed by an other method than the + # current one. Take the value on the record. + component_qties = { + p.id: { + stock_available_mrp_based_on: p[ + stock_available_mrp_based_on]} for p in + component_products} + + for product in product_with_bom: + # Need by product (same product can be in many BOM lines/levels) + exploded_components = exploded_boms[product.id] + component_needs = product._get_components_needs( + exploded_components) + if not component_needs: + # The BoM has no line we can use + potential_qty = 0.0 + + else: + # Find the lowest quantity we can make with the stock at hand + components_potential_qty = min( + [component_qties[component.id][ + stock_available_mrp_based_on] // need + for component, need in component_needs.items()] + ) + + potential_qty = (product.bom_id.product_qty * + components_potential_qty) + + res[product.id]['potential_qty'] = potential_qty + res[product.id]['immediately_usable_qty'] += potential_qty + + return res + + @api.multi + def _explode_boms(self): + """ + return a dict by product_id of exploded bom lines + :return: + """ + exploded_boms = {} + for rec in self: + exploded_boms[rec.id] = rec.bom_id.explode(rec, 1.0)[1] + return exploded_boms + + @api.model + def _get_components_needs(self, exploded_components): + """ Return the needed qty of each compoments in the exploded_components + + :type exploded_components :rtype: collections.Counter """ - bom_obj = self.env['mrp.bom'] - uom_obj = self.env['product.uom'] - product_obj = self.env['product.product'] - needs = Counter() - for bom_component in bom_obj._bom_explode(bom, product, 1.0)[0]: - product_uom = uom_obj.browse(bom_component['product_uom']) - component = product_obj.browse(bom_component['product_id']) - - component_qty = uom_obj._compute_qty_obj( - product_uom, - bom_component['product_qty'], - component.uom_id, - ) - needs += Counter( - {component: component_qty} - ) + for bom_component in exploded_components: + component = bom_component[0].product_id + needs += Counter({component: bom_component[1]['qty']}) return needs - - def _get_component_ids(self): - """ Compute component_ids by getting all the components for - this product. - """ - bom_obj = self.env['mrp.bom'] - - bom_id = bom_obj._bom_find(product_id=self.id) - if bom_id: - bom = bom_obj.browse(bom_id) - for bom_component in bom_obj._bom_explode(bom, self, 1.0)[0]: - self.component_ids |= self.browse(bom_component['product_id']) diff --git a/stock_available_mrp/models/product_template.py b/stock_available_mrp/models/product_template.py index f87ef6901..b5eb79cf7 100644 --- a/stock_available_mrp/models/product_template.py +++ b/stock_available_mrp/models/product_template.py @@ -2,45 +2,17 @@ # © 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import models, fields, api -from openerp.addons import decimal_precision as dp +from odoo import models, api class ProductTemplate(models.Model): _inherit = 'product.template' - potential_qty = fields.Float( - compute='_get_potential_qty', - type='float', - digits_compute=dp.get_precision('Product Unit of Measure'), - string='Potential', - help="Quantity of this Product that could be produced using " - "the materials already at hand. " - "If the product has several variants, this will be the biggest " - "quantity that can be made for a any single variant.") - @api.multi - @api.depends('potential_qty') - def _immediately_usable_qty(self): - """Add the potential quantity to the quantity available to promise. - - This is the same implementation as for variants.""" - super(ProductTemplate, self)._immediately_usable_qty() - for tmpl in self: - tmpl.immediately_usable_qty += tmpl.potential_qty - - @api.multi - @api.depends('product_variant_ids.potential_qty') - def _get_potential_qty(self): - """Compute the potential as the max of all the variants's potential. - - We can't add the potential of variants: if they share components we - may not be able to make all the variants. - So we set the arbitrary rule that we can promise up to the biggest - variant's potential. - """ - for tmpl in self: - if not tmpl.product_variant_ids: - continue - tmpl.potential_qty = max( - [v.potential_qty for v in tmpl.product_variant_ids]) + def _compute_available_quantities_dict(self): + res = super(ProductTemplate, self)._compute_available_quantities_dict() + for template in self: + if template.bom_ids: + res[template.id]['immediately_usable_qty'] =\ + res[template.id]['potential_qty'] + return res diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index 896655dae..83cfba1ef 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -2,8 +2,8 @@ # © 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp.tests.common import TransactionCase -from openerp.osv.expression import TRUE_LEAF +from odoo.tests.common import TransactionCase +from odoo.osv.expression import TRUE_LEAF class TestPotentialQty(TransactionCase): @@ -23,21 +23,19 @@ class TestPotentialQty(TransactionCase): def setup_demo_data(self): #  An interesting product (multi-line BoM, variants) self.tmpl = self.browse_ref( - 'product.product_product_4_product_template') + 'mrp.product_product_build_kit_product_template') #  First variant - self.var1 = self.browse_ref('product.product_product_4c') + self.var1 = self.browse_ref('mrp.product_product_build_kit') #  Second variant - self.var2 = self.browse_ref('product.product_product_4') + self.var2 = self.browse_ref( + 'stock_available_mrp.product_kit_1a') # Components that can be used to make the product component_ids = [ - # CPUa8 - self.ref('product.product_product_23'), - # RAM-SR2 - self.ref('product.product_product_14'), - # HDD SH-2 replaces RAM-SR2 through our demo phantom BoM - self.ref('product.product_product_18'), - # RAM-SR3 - self.ref('product.product_product_15')] + # KeyBoard + self.ref('product.product_product_9'), + # Mouse + self.ref('product.product_product_12'), + ] # Zero-out the inventory of all variants and components for component_id in ( @@ -53,7 +51,7 @@ class TestPotentialQty(TransactionCase): inventory.action_done() #  A product without a BoM - self.product_wo_bom = self.browse_ref('product.product_product_23') + self.product_wo_bom = self.browse_ref('product.product_product_11') # Record the initial quantity available for sale self.initial_usable_qties = {i.id: i.immediately_usable_qty @@ -92,14 +90,12 @@ class TestPotentialQty(TransactionCase): 'product_tmpl_id': product.product_tmpl_id.id, 'product_id': product.id, 'product_qty': product_qty, - 'product_uom': self.ref('product.product_uom_unit'), 'routing_id': routing_id, }) self.bom_line_model.create({ 'bom_id': bom.id, 'product_id': sub_product.id, 'product_qty': sub_product_qty, - 'product_uom': self.ref('product.product_uom_unit'), }) return bom @@ -122,7 +118,7 @@ class TestPotentialQty(TransactionCase): def test_potential_qty_no_bom_for_company(self): chicago_id = self.ref('stock.res_company_1') - # Receive 1000x CPUa8s owned by Chicago + # Receive 1000x CPUI5s owned by Chicago inventory = self.env['stock.inventory'].create( {'name': 'Receive CPUa8', 'company_id': chicago_id, @@ -132,12 +128,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_23'), + 'product_id': self.ref('product.product_product_9'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) inventory.action_done() - # Put RAM-SR3 owned by Chicago for 1000x the 1st variant in main WH + # Put RAM-SR5 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, @@ -147,7 +143,7 @@ class TestPotentialQty(TransactionCase): self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'company_id': chicago_id, - 'product_id': self.ref('product.product_product_15'), + 'product_id': self.ref('product.product_product_12'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) inventory.action_done() @@ -187,44 +183,21 @@ class TestPotentialQty(TransactionCase): self.assertPotentialQty( test_user_tmpl, 1000.0, '') - def test_group_mrp_missing(self): - test_user = self.env['res.users'].create({ - 'name': 'test_demo', - 'login': 'test_demo', - 'company_id': self.ref('base.main_company'), - 'company_ids': [(4, self.ref('base.main_company'))], - 'groups_id': [(4, self.ref('stock.group_stock_user'))], - }) - - p1 = self.product_model.create({'name': 'Test P1'}) - p2 = self.product_model.create({'name': 'Test P2'}) - - self.create_simple_bom(p1, p2, - routing_id=self.ref('mrp.mrp_routing_0')) - self.create_inventory(p2.id, 1) - - test_user_p1 = p1.sudo(test_user) - # Test user doesn't have access to mrp_routing, can't compute potential - self.assertEqual(0, test_user_p1.potential_qty) - - test_user.groups_id = [(4, self.ref('mrp.group_mrp_user'))] - self.assertEqual(1, test_user_p1.potential_qty) - def test_potential_qty(self): for i in [self.tmpl, self.var1, self.var2]: self.assertPotentialQty( i, 0.0, "The potential quantity should start at 0") - # Receive 1000x CPUa8s + # Receive 1000x Mouses inventory = self.env['stock.inventory'].create( - {'name': 'Receive CPUa8', + {'name': 'Receive Mouses', 'location_id': self.wh_main.lot_stock_id.id, 'filter': 'partial'}) inventory.prepare_inventory() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_23'), + 'product_id': self.ref('product.product_product_12'), 'location_id': self.wh_main.lot_stock_id.id, 'product_qty': 1000.0}) inventory.action_done() @@ -234,7 +207,7 @@ class TestPotentialQty(TransactionCase): "Receiving a single component should not change the " "potential of %s" % i) - # Receive enough RAM-SR3 to make 1000x the 1st variant in main WH + # Receive enough keyboard 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, @@ -242,7 +215,8 @@ class TestPotentialQty(TransactionCase): inventory.prepare_inventory() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_15'), + 'product_id': self.ref( + 'product.product_product_9'), 'location_id': self.wh_main.lot_stock_id.id, 'product_qty': 1000.0}) inventory.action_done() @@ -257,9 +231,7 @@ class TestPotentialQty(TransactionCase): "Receiving variant 1's component should not change " "variant 2's potential") - # Receive enough components to make 42X the 2nd variant at Chicago - # need 13 dozens of HDD with 50% efficiency to build 42 RAM - # So 313 HDD (with rounding) for 42 RAM + # Receive enough components to make 313 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, @@ -267,12 +239,13 @@ class TestPotentialQty(TransactionCase): inventory.prepare_inventory() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_23'), + 'product_id': self.ref('product.product_product_12'), '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('product.product_product_18'), + 'product_id': self.ref( + 'stock_available_mrp.product_product_9_white'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 313.0}) inventory.action_done() @@ -284,14 +257,14 @@ class TestPotentialQty(TransactionCase): "Receiving variant 2's component should not change " "variant 1's potential") self.assertPotentialQty( - self.var2, 42.0, + self.var2, 313.0, "Wrong variant 2 potential after receiving components") # Check by warehouse self.assertPotentialQty( self.tmpl.with_context(warehouse=self.wh_main.id), 1000.0, "Wrong potential quantity in main WH") self.assertPotentialQty( - self.tmpl.with_context(warehouse=self.wh_ch.id), 42.0, + self.tmpl.with_context(warehouse=self.wh_ch.id), 313.0, "Wrong potential quantity in Chicago WH") # Check by location self.assertPotentialQty( @@ -301,7 +274,7 @@ class TestPotentialQty(TransactionCase): self.assertPotentialQty( self.tmpl.with_context( location=self.wh_ch.lot_stock_id.id), - 42.0, + 313.0, "Wrong potential quantity in Chicago WH location") def test_multi_unit_recursive_bom(self): @@ -324,12 +297,10 @@ class TestPotentialQty(TransactionCase): 'product_id': p1.id, }) - # 1 dozen of component self.bom_line_model.create({ 'bom_id': bom_p1.id, 'product_id': p3.id, 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_dozen'), }) # Two p2 which have a bom @@ -337,7 +308,6 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p1.id, 'product_id': p2.id, 'product_qty': 2, - 'product_uom': self.ref('product.product_uom_unit'), }) bom_p2 = self.bom_model.create({ @@ -351,12 +321,11 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p2.id, 'product_id': p3.id, 'product_qty': 2, - 'product_uom': self.ref('product.product_uom_unit'), }) p1.refresh() - # Need a least 1 dozen + 2 * 2 = 16 units for one P1 + # Need a least 5 units for one P1 self.assertEqual(0, p1.potential_qty) self.create_inventory(p3.id, 1) @@ -364,89 +333,30 @@ class TestPotentialQty(TransactionCase): p1.refresh() self.assertEqual(0, p1.potential_qty) - self.create_inventory(p3.id, 15) + self.create_inventory(p3.id, 3) p1.refresh() self.assertEqual(0, p1.potential_qty) - self.create_inventory(p3.id, 16) + self.create_inventory(p3.id, 5) p1.refresh() self.assertEqual(1.0, p1.potential_qty) - self.create_inventory(p3.id, 25) + self.create_inventory(p3.id, 6) p1.refresh() self.assertEqual(1.0, p1.potential_qty) - self.create_inventory(p3.id, 32) + self.create_inventory(p3.id, 10) p1.refresh() self.assertEqual(2.0, p1.potential_qty) - def test_bom_qty_and_efficiency(self): - - p1 = self.product_model.create({ - 'name': 'Test product with BOM', - }) - - p2 = self.product_model.create({ - 'name': 'Test sub product with BOM', - }) - - p3 = self.product_model.create({ - 'name': 'Test component' - }) - - # A bom produce 2 dozen of P1 - bom_p1 = self.bom_model.create({ - 'product_tmpl_id': p1.product_tmpl_id.id, - 'product_id': p1.id, - 'product_qty': 2, - 'product_uom': self.ref('product.product_uom_dozen'), - }) - - # Need 5 p2 for that - self.bom_line_model.create({ - 'bom_id': bom_p1.id, - 'product_id': p2.id, - 'product_qty': 5, - 'product_uom': self.ref('product.product_uom_unit'), - 'product_efficiency': 0.8, - }) - - # Which need 1 dozen of P3 - bom_p2 = self.bom_model.create({ - 'product_tmpl_id': p2.product_tmpl_id.id, - 'product_id': p2.id, - 'type': 'phantom', - }) - self.bom_line_model.create({ - 'bom_id': bom_p2.id, - 'product_id': p3.id, - 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_dozen'), - }) - - p1.refresh() - self.assertEqual(0, p1.potential_qty) - - self.create_inventory(p3.id, 60) - - p1.refresh() - self.assertEqual(0, p1.potential_qty) - - # Need 5 * 1 dozen => 60 - # But 80% lost each dozen, need 3 more by dozen => 60 + 5 *3 => 75 - self.create_inventory(p3.id, 75) - - p1.refresh() - self.assertEqual(24, p1.potential_qty) - def test_component_stock_choice(self): # Test to change component stock for compute BOM stock # Get a demo product with outgoing move (qty: 3) - imac = self.browse_ref('product.product_product_8') + prod = self.browse_ref('product.product_product_20') # Set on hand qty - self.create_inventory(imac.id, 3) + self.create_inventory(prod.id, 3) # Create a product with BOM p1 = self.product_model.create({ @@ -456,15 +366,13 @@ class TestPotentialQty(TransactionCase): 'product_tmpl_id': p1.product_tmpl_id.id, 'product_id': p1.id, 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_unit'), }) - # Need 1 iMac for that + # Need 1 prod for that self.bom_line_model.create({ 'bom_id': bom_p1.id, - 'product_id': imac.id, + 'product_id': prod.id, 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_unit'), }) # Default component is qty_available @@ -479,25 +387,23 @@ class TestPotentialQty(TransactionCase): self.assertEqual(0.0, p1.potential_qty) # If iMac has a Bom and can be manufactured - imac_component = self.product_model.create({ - 'name': 'iMac component', + component = self.product_model.create({ + 'name': 'component', }) - self.create_inventory(imac_component.id, 5) + self.create_inventory(component.id, 5) imac_bom = self.bom_model.create({ - 'product_tmpl_id': imac.product_tmpl_id.id, - 'product_id': imac.id, + 'product_tmpl_id': prod.product_tmpl_id.id, + 'product_id': prod.id, 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_unit'), 'type': 'phantom', }) - # Need 1 imac_component for iMac + # Need 1 component for prod self.bom_line_model.create({ 'bom_id': imac_bom.id, - 'product_id': imac_component.id, + 'product_id': component.id, 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_unit'), }) p1.refresh() diff --git a/stock_available_mrp/views/product_template_view.xml b/stock_available_mrp/views/product_template_view.xml index 446e0c5c3..fa20ec54b 100644 --- a/stock_available_mrp/views/product_template_view.xml +++ b/stock_available_mrp/views/product_template_view.xml @@ -1,6 +1,5 @@ - - + Potential quantity on product form @@ -23,5 +22,4 @@ - - +