From de4a5dfa16939e5f72673dac4a63e5bf54c48989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 13 Oct 2015 16:59:05 +0200 Subject: [PATCH] [MOV] move addons out of __unported__ (they remain not installable) --- stock_available_mrp/README.rst | 48 +++++++ stock_available_mrp/__init__.py | 21 +++ stock_available_mrp/__openerp__.py | 35 +++++ stock_available_mrp/i18n/fr.po | 34 +++++ .../i18n/stock_available_mrp.pot | 34 +++++ stock_available_mrp/product.py | 126 ++++++++++++++++++ stock_available_mrp/product_view.xml | 19 +++ stock_available_mrp/test/potential_qty.yml | 70 ++++++++++ 8 files changed, 387 insertions(+) create mode 100644 stock_available_mrp/README.rst create mode 100644 stock_available_mrp/__init__.py create mode 100644 stock_available_mrp/__openerp__.py create mode 100644 stock_available_mrp/i18n/fr.po create mode 100644 stock_available_mrp/i18n/stock_available_mrp.pot create mode 100644 stock_available_mrp/product.py create mode 100644 stock_available_mrp/product_view.xml create mode 100644 stock_available_mrp/test/potential_qty.yml diff --git a/stock_available_mrp/README.rst b/stock_available_mrp/README.rst new file mode 100644 index 000000000..99e7569b8 --- /dev/null +++ b/stock_available_mrp/README.rst @@ -0,0 +1,48 @@ +Consider the production potential is available to promise +========================================================= + +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. + +Known issues +============ + +The manufacturing delays are not taken into account : this module assumes that +if you have components in stock goods, you can manufacture finished goods +quickly enough. +To avoid overestimating, **only the first level** of Bill of Materials is +considered. + +Roadmap +------- + +* include all levels of BoM, using `bom_explode`. @gdgellatly gave an example + of how to do it here: https://github.com/OCA/stock-logistics-warehouse/pull/5#issuecomment-66902191 + Ideally, we will want to take manufacturing delays into account: we can't + promiss goods to customers if they want them delivered earlier that we can + make them +* add an option (probably as a sub-module) to consider all raw materials as + available if they can be bought from the suppliers in time for the + manufacturing. + +Credits +======= + +Contributors +------------ +* Loïc Bellier (Numérigraphe) +* Lionel Sausin (Numérigraphe) + +Maintainer +---------- + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. 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..7e00810e0 --- /dev/null +++ b/stock_available_mrp/__openerp__.py @@ -0,0 +1,35 @@ +# -*- 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,Odoo Community Association (OCA)", + 'category': 'Hidden', + 'depends': ['stock_available', 'mrp'], + 'data': [ + 'product_view.xml', + ], + 'test': [ + 'test/potential_qty.yml', + ], + 'license': 'AGPL-3', + 'installable': False +} 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..f36b86a5c --- /dev/null +++ b/stock_available_mrp/product.py @@ -0,0 +1,126 @@ +# -*- 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=None): + """Compute the potential qty from BoMs with components available""" + bom_obj = self.pool['mrp.bom'] + uom_obj = self.pool['product.uom'] + if context is None: + context = {} + 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)