[ADD] stock_available_mrp

Module to take immediate manufaturing capability into account in the stock quantity available to promise.

Conflicts:
	stock_available/res_config.py
This commit is contained in:
Lionel Sausin (Numérigraphe)
2014-11-28 17:40:27 +01:00
committed by Lionel Sausin
parent 8504307c7b
commit 13c845fae7
9 changed files with 354 additions and 0 deletions

View File

@@ -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")

View File

@@ -15,6 +15,10 @@
<field name="module_stock_available_immediately" class="oe_inline" />
<label for="module_stock_available_immediately" />
</div>
<div>
<field name="module_stock_available_mrp" class="oe_inline" />
<label for="module_stock_available_mrp" />
</div>
</div>
</group>
</xpath>

View File

@@ -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

View File

@@ -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',
}

View File

@@ -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."

View File

@@ -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 ""

View File

@@ -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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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."),
}

View File

@@ -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>

View File

@@ -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)