← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-mrp into lp:stock-logistic-warehouse

 

Lionel Sausin - Numérigraphe has proposed merging lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-mrp into lp:stock-logistic-warehouse with lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-sale as a prerequisite.

Requested reviews:
  Stock and Logistic Core Editors (stock-logistic-core-editors)

For more details, see:
https://code.launchpad.net/~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-mrp/+merge/220764

Add stock_available_mrp: take immediate manufaturing capability into account in the stock quantity available to promise

This branch builds upon lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available, which adds a generic module to compute the stock available to promise in a configurable way.

It also includes lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-sale, which adds another //unrelated// implementation. This is only to avoid merge conflicts in case both are accepted, but they are independent works I can rebase this branch if needed.

Module by Loïc Bellier with contributions from your humble servant.
-- 
https://code.launchpad.net/~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-mrp/+merge/220764
Your team Stock and Logistic Core Editors is requested to review the proposed merge of lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available-mrp into lp:stock-logistic-warehouse.
=== modified file 'stock_available/res_config.py'
--- stock_available/res_config.py	2014-05-23 07:58:49 +0000
+++ stock_available/res_config.py	2014-05-23 07:58:49 +0000
@@ -38,4 +38,12 @@
                  "This installs the modules stock_available_sale.\n"
                  "If the modules sale and sale_delivery_date are not "
                  "installed, this will install them too"),
+        'module_stock_available_mrp': fields.boolean(
+            'Include the production potential',
+            help="This will add the quantities of goods that can be"
+                 "immediately manufactured, to the quantities available to"
+                 "promise.\n"
+                 "This installs the module stock_available_mrp.\n"
+                 "If the module mrp is not installed, this will install it "
+                 "too"),
     }

=== modified file 'stock_available/res_config_view.xml'
--- stock_available/res_config_view.xml	2014-05-23 07:58:49 +0000
+++ stock_available/res_config_view.xml	2014-05-23 07:58:49 +0000
@@ -19,6 +19,10 @@
                                     <field name="module_stock_available_sale" class="oe_inline" />
                                     <label for="module_stock_available_sale" />
                                 </div>
+                                <div>
+                                    <field name="module_stock_available_mrp" class="oe_inline" />
+                                    <label for="module_stock_available_mrp" />
+                                </div>
                             </div>
                         </group>
                     </xpath>

=== added directory 'stock_available_mrp'
=== added file 'stock_available_mrp/__init__.py'
--- stock_available_mrp/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_available_mrp/__init__.py	2014-05-23 07:58:49 +0000
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import product

=== added file 'stock_available_mrp/__openerp__.py'
--- stock_available_mrp/__openerp__.py	1970-01-01 00:00:00 +0000
+++ stock_available_mrp/__openerp__.py	2014-05-23 07:58:49 +0000
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    'name': 'Consider the production potential is available to promise',
+    'version': '2.0',
+    'author': u'Numérigraphe',
+    'category': 'Hidden',
+    'depends': ['stock_available', 'mrp'],
+    'description': """
+This module takes the potential quantities available for Products in account in
+the quantity available to promise, where the "Potential quantity" is the
+quantity that can be manufactured with the components immediately at hand,
+following a single level of Bill of Materials.""",
+    'data': [
+        'product_view.xml',
+    ],
+    'test': [
+        'test/potential_qty.yml',
+    ],
+    'license': 'AGPL-3',
+}

=== added file 'stock_available_mrp/product.py'
--- stock_available_mrp/product.py	1970-01-01 00:00:00 +0000
+++ stock_available_mrp/product.py	2014-05-23 07:58:49 +0000
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import orm, fields
+import decimal_precision as dp
+
+
+class product_product(orm.Model):
+    """Add the computation for the stock available to promise"""
+    _inherit = 'product.product'
+
+    def _product_available(self, cr, uid, ids, field_names=None, arg=False,
+                           context=None):
+        """Compute the potential quantities available to promise."""
+        # Check the context
+        if context is None:
+            context = {}
+        # Prepare an alternative context without 'uom', to avoid cross-category
+        # conversions when reading the available stock of components
+        if 'uom' in context:
+            context_wo_uom = context.copy()
+            del context_wo_uom['uom']
+        else:
+            context_wo_uom = context
+
+        if field_names is None:
+            field_names = []
+
+        # Compute the core quantities
+        res = super(product_product, self)._product_available(
+            cr, uid, ids, field_names=field_names, arg=arg, context=context)
+
+        # Compute the quantities quoted/potential/available to promise
+        if ('potential_qty' in field_names):
+            # Compute the potential qty from BoMs with components available
+            bom_obj = self.pool['mrp.bom']
+            to_uom = 'uom' in context and self.pool['product.uom'].browse(
+                cr, uid, context['uom'], context=context)
+
+            for product in self.browse(cr, uid, ids, context=context):
+                # _bom_find() returns a single BoM id.
+                # We will not check any other BoM for this product
+                bom_id = bom_obj._bom_find(cr, uid, product.id,
+                                           product.uom_id.id)
+                if bom_id:
+                    min_qty = self._compute_potential_qty_from_bom(
+                        cr, uid, bom_id, to_uom or product.uom_id,
+                        context=context)
+
+                    res[product.id]['potential_qty'] += min_qty
+                    if ('immediately_usable_qty' in field_names):
+                        res[product.id]['immediately_usable_qty'] += min_qty
+
+        return self._update_virtual_available(cr, uid, res, context=context)
+
+    def _compute_potential_qty_from_bom(self, cr, uid, bom_id, to_uom,
+                                        context):
+        """Compute the potential qty from BoMs with components available"""
+        bom_obj = self.pool['mrp.bom']
+        uom_obj = self.pool['product.uom']
+        if 'uom' in context:
+            context_wo_uom = context.copy()
+            del context_wo_uom['uom']
+        else:
+            context_wo_uom = context
+        min_qty = False
+        # Browse ignoring the UoM context to avoid cross-category conversions
+        final_product = bom_obj.browse(
+            cr, uid, [bom_id], context=context_wo_uom)[0]
+
+        # store id of final product uom
+
+        for component in final_product.bom_lines:
+            # qty available in BOM line's UoM
+            # XXX use context['uom'] instead?
+            stock_component_qty = uom_obj._compute_qty_obj(
+                cr, uid,
+                component.product_id.uom_id,
+                component.product_id.virtual_available,
+                component.product_uom)
+            # qty we can produce with this component, in the BoM's UoM
+            recipe_uom_qty = (stock_component_qty // component.product_qty
+                              ) * final_product.product_qty
+            # Convert back to the reporting default UoM
+            stock_product_uom_qty = uom_obj._compute_qty_obj(
+                cr, uid, final_product.product_uom, recipe_uom_qty, to_uom)
+            if min_qty is False:
+                min_qty = stock_product_uom_qty
+            elif stock_product_uom_qty < min_qty:
+                min_qty = stock_product_uom_qty
+        if min_qty < 0.0:
+            min_qty = 0.0
+        return min_qty
+
+    _columns = {
+        'potential_qty': fields.function(
+            _product_available, method=True, multi='qty_available',
+            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, following a single level"
+                 "of the Bills of Materials."),
+    }

=== added file 'stock_available_mrp/product_view.xml'
--- stock_available_mrp/product_view.xml	1970-01-01 00:00:00 +0000
+++ stock_available_mrp/product_view.xml	2014-05-23 07:58:49 +0000
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+	<data>
+		<!-- Add the quantity available to promise in the product form -->
+		<record id="view_product_form_potential_qty" model="ir.ui.view">
+			<field name="name">product.form.potential_qty</field>
+			<field name="model">product.product</field>
+			<field name="type">form</field>
+			<field name="inherit_id" ref="stock.view_normal_procurement_locations_form" />
+			<field name="arch" type="xml">
+				<data>
+                    <xpath expr="//field[@name='virtual_available']" position="after">
+                    	<field name="potential_qty"/>
+                    </xpath>
+				</data>
+			</field>
+		</record>
+	</data>
+</openerp>

=== added directory 'stock_available_mrp/test'
=== added file 'stock_available_mrp/test/potential_qty.yml'
--- stock_available_mrp/test/potential_qty.yml	1970-01-01 00:00:00 +0000
+++ stock_available_mrp/test/potential_qty.yml	2014-05-23 07:58:49 +0000
@@ -0,0 +1,68 @@
+- Test the computation of the potential quantity on product_product_16, a product with several multi-line BoMs
+
+- Create a UoM in the category of PCE
+- !record {model: product.uom, id: thousand}:
+    name: Thousand
+    factor: 0.001
+    rounding: 0.0001
+    uom_type: bigger
+    category_id: product.product_uom_categ_unit
+
+- Receive enough of the first component to run the BoM 1000x, and check that the potential is unchanged
+- !python {model: mrp.bom}: |
+    bom = self.browse(
+      cr, uid,
+      self._bom_find(
+        cr, uid, ref('product.product_product_16'),
+        ref('product.product_uom_unit')))
+    assert len(bom.bom_lines)>1, "The test BoM has a single line, two or more are needed for the test"
+    initial_qty = bom.product_id.potential_qty
+    component = bom.bom_lines[0]
+    assert component.product_uom.category_id.id == ref('product.product_uom_categ_unit'), "The first component's UoM is in the wrong category can't test"
+    self.pool['stock.move'].create(
+      cr, uid,
+      {
+        'name': 'Receive first component',
+        'product_id': component.product_id.id,
+        'product_qty': component.product_qty * 1000.0,
+        'product_uom': component.product_id.uom_id.id,
+        'location_id': ref('stock.stock_location_suppliers'),
+        'location_dest_id': ref('stock.stock_location_stock'),
+        'state': 'done',
+      })
+    # Re-read the potential quantity
+    new_qty = self.browse(cr, uid, bom.id).product_id.potential_qty
+    assert new_qty == initial_qty, "Receiving a single component should not change the potential qty (%s instead of %s)" % (new_qty, initial_qty)
+
+- Receive enough of all the components to run the BoM 1000x and check that the potential is correct
+- !python {model: mrp.bom}: |
+    # Select a BoM for product_product_16
+    bom = self.browse(
+      cr, uid,
+      self._bom_find(
+        cr, uid, ref('product.product_product_16'),
+        ref('product.product_uom_unit')))
+    assert len(bom.bom_lines)>1, "The test BoM has a single line, two or more are needed for the test"
+    initial_qty = bom.product_id.potential_qty
+    for component in bom.bom_lines:
+      assert component.product_uom.category_id.id == ref('product.product_uom_categ_unit'), "The first component's UoM is in the wrong category can't test"
+      x = {
+          'name': 'Receive all components',
+          'product_id': component.product_id.id,
+          'product_qty': component.product_qty * 1000.0,
+          'product_uom': component.product_id.uom_id.id,
+          'location_id': ref('stock.stock_location_suppliers'),
+          'location_dest_id': ref('stock.stock_location_stock'),
+          'state': 'done',
+        }
+      self.pool['stock.move'].create(
+        cr, uid,x)
+    # Re-read the potential quantity
+    new_qty = self.browse(cr, uid, bom.id).product_id.potential_qty
+    right_qty = initial_qty + bom.product_qty * 1000.0
+    assert new_qty == right_qty, "The potential qty is incorrect after receiveing all the components (%s instead of %s)" % (new_qty, right_qty)
+    # Re-read the potential quantity with a different UoM in the context
+    new_qty = self.browse(
+      cr, uid, bom.id, context={'uom': ref('thousand')}).product_id.potential_qty
+    right_qty = initial_qty / 1000.0 + bom.product_qty
+    assert abs(new_qty - right_qty) < 0.0001, "The potential qty is incorrect with another UoM in the context (%s instead of %s)" % (new_qty, right_qty)


Follow ups