openerp-community-reviewer team mailing list archive
-
openerp-community-reviewer team
-
Mailing list archive
-
Message #06934
[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