diff --git a/stock_available/res_config.py b/stock_available/res_config.py
index bf78bcb3b..7d4355df7 100644
--- a/stock_available/res_config.py
+++ b/stock_available/res_config.py
@@ -30,3 +30,12 @@ class StockConfig(models.TransientModel):
help="This will subtract incoming quantities from the quantities "
"available to promise.\n"
"This installs the module stock_available_immediately.")
+
+ module_stock_available_mrp = fields.Boolean(
+ string='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")
diff --git a/stock_available/res_config_view.xml b/stock_available/res_config_view.xml
index 15e45c031..d4f378fb8 100644
--- a/stock_available/res_config_view.xml
+++ b/stock_available/res_config_view.xml
@@ -15,6 +15,10 @@
+
+
+
+
diff --git a/stock_available_mrp/__init__.py b/stock_available_mrp/__init__.py
new file mode 100644
index 000000000..302cef51c
--- /dev/null
+++ b/stock_available_mrp/__init__.py
@@ -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 .
+#
+##############################################################################
+
+from . import product
diff --git a/stock_available_mrp/__openerp__.py b/stock_available_mrp/__openerp__.py
new file mode 100644
index 000000000..aaba2b727
--- /dev/null
+++ b/stock_available_mrp/__openerp__.py
@@ -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 .
+#
+##############################################################################
+
+{
+ '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',
+}
diff --git a/stock_available_mrp/i18n/fr.po b/stock_available_mrp/i18n/fr.po
new file mode 100644
index 000000000..860df2cc3
--- /dev/null
+++ b/stock_available_mrp/i18n/fr.po
@@ -0,0 +1,34 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * stock_available_mrp
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-07-30 19:29+0000\n"
+"PO-Revision-Date: 2014-07-30 19:29+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: stock_available_mrp
+#: field:product.product,potential_qty:0
+msgid "Potential"
+msgstr "Potentiel"
+
+#. module: stock_available_mrp
+#: code:_description:0
+#: model:ir.model,name:stock_available_mrp.model_product_product
+#, python-format
+msgid "Product"
+msgstr "Article"
+
+#. module: stock_available_mrp
+#: help:product.product,potential_qty:0
+msgid "Quantity of this Product that could be produced using the materials already at hand, following a single level of the Bills of Materials."
+msgstr "Quantité de cet article que l'on pourrait produire en utilisant les produits déjà disponibles, en suivant un seul niveau de nomenclature."
+
diff --git a/stock_available_mrp/i18n/stock_available_mrp.pot b/stock_available_mrp/i18n/stock_available_mrp.pot
new file mode 100644
index 000000000..b1cffcca2
--- /dev/null
+++ b/stock_available_mrp/i18n/stock_available_mrp.pot
@@ -0,0 +1,34 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * stock_available_mrp
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-07-30 19:41+0000\n"
+"PO-Revision-Date: 2014-07-30 19:41+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: stock_available_mrp
+#: field:product.product,potential_qty:0
+msgid "Potential"
+msgstr ""
+
+#. module: stock_available_mrp
+#: code:_description:0
+#: model:ir.model,name:stock_available_mrp.model_product_product
+#, python-format
+msgid "Product"
+msgstr ""
+
+#. module: stock_available_mrp
+#: help:product.product,potential_qty:0
+msgid "Quantity of this Product that could be produced using the materials already at hand, following a single level of the Bills of Materials."
+msgstr ""
+
diff --git a/stock_available_mrp/product.py b/stock_available_mrp/product.py
new file mode 100644
index 000000000..4d5cd2f6f
--- /dev/null
+++ b/stock_available_mrp/product.py
@@ -0,0 +1,124 @@
+# -*- 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 .
+#
+##############################################################################
+
+from openerp import SUPERUSER_ID
+from openerp.osv import orm, fields
+import openerp.addons.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):
+ """Quantity available to promise based on components at hand."""
+ # Compute the core quantities
+ res = super(product_product, self)._product_available(
+ cr, uid, ids, field_names=field_names, arg=arg, context=context)
+ # If we didn't get a field_names list, there's nothing to do
+ if field_names is None:
+ return res
+
+ 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
+
+ # Compute the production capacity
+ if any([f in field_names
+ for f in ['potential_qty', 'immediately_usable_qty']]):
+ # 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, SUPERUSER_ID, 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, SUPERUSER_ID, 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)
+
+ if 'potential_qty' in field_names:
+ res[product.id]['potential_qty'] += min_qty
+ if 'immediately_usable_qty' in field_names:
+ res[product.id]['immediately_usable_qty'] += min_qty
+
+ return res
+
+ 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
+ bom = bom_obj.browse(
+ cr, uid, [bom_id], context=context_wo_uom)[0]
+
+ # store id of final product uom
+
+ for component in bom.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
+ bom_uom_qty = (stock_component_qty // component.product_qty
+ ) * bom.product_qty
+ # Convert back to the reporting default UoM
+ stock_product_uom_qty = uom_obj._compute_qty_obj(
+ cr, uid, bom.product_uom, bom_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."),
+ }
diff --git a/stock_available_mrp/product_view.xml b/stock_available_mrp/product_view.xml
new file mode 100644
index 000000000..707df74ff
--- /dev/null
+++ b/stock_available_mrp/product_view.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ product.form.potential_qty
+ product.product
+ form
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stock_available_mrp/test/potential_qty.yml b/stock_available_mrp/test/potential_qty.yml
new file mode 100644
index 000000000..352254900
--- /dev/null
+++ b/stock_available_mrp/test/potential_qty.yml
@@ -0,0 +1,70 @@
+- 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
+ bom.refresh()
+ new_qty = bom.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"
+ self.pool['stock.move'].create(
+ cr, uid,
+ {
+ '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',
+ })
+ # Re-read the potential quantity
+ bom.refresh()
+ new_qty = bom.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)