From 2dc71233be8de5e6d265cd8622e823f0e1b0c11d 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 01/29] [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) From e13f22c1ca2aeea3c0553aa5b13001ef56ebbc51 Mon Sep 17 00:00:00 2001 From: Lionel Sausin Date: Fri, 20 Nov 2015 17:54:53 +0100 Subject: [PATCH 02/29] [MIGR] stock_available_mrp: migrate to v8 Compute potential quantities for both product templates and variants. To keep the code simple, only the biggest potential of any single variant is accounted for in the template's potential. Take all levels of phantom BoM into account, respects validity dates etc. thanks to the use of the standard method _bom_explode, as suggested by @gdgellatly in https://github.com/OCA/stock-logistics-warehouse/pull/5#issuecomment-66902191 Improve tests, rewritten in python. Adhere to new file/manifest/README conventions. Simplify copyright headers --- stock_available_mrp/README.rst | 64 +++++-- stock_available_mrp/__init__.py | 22 +-- stock_available_mrp/__openerp__.py | 31 +--- stock_available_mrp/demo/mrp_bom.yml | 28 +++ stock_available_mrp/i18n/fr.po | 5 +- .../i18n/stock_available_mrp.pot | 3 +- stock_available_mrp/models/__init__.py | 6 + stock_available_mrp/models/product_product.py | 58 ++++++ .../models/product_template.py | 46 +++++ stock_available_mrp/product.py | 126 ------------- stock_available_mrp/product_view.xml | 19 -- stock_available_mrp/test/potential_qty.yml | 70 -------- stock_available_mrp/tests/__init__.py | 5 + .../tests/test_potential_qty.py | 165 ++++++++++++++++++ .../views/product_template_view.xml | 19 ++ 15 files changed, 389 insertions(+), 278 deletions(-) create mode 100644 stock_available_mrp/demo/mrp_bom.yml create mode 100644 stock_available_mrp/models/__init__.py create mode 100644 stock_available_mrp/models/product_product.py create mode 100644 stock_available_mrp/models/product_template.py delete mode 100644 stock_available_mrp/product.py delete mode 100644 stock_available_mrp/product_view.xml delete mode 100644 stock_available_mrp/test/potential_qty.yml create mode 100644 stock_available_mrp/tests/__init__.py create mode 100644 stock_available_mrp/tests/test_potential_qty.py create mode 100644 stock_available_mrp/views/product_template_view.xml diff --git a/stock_available_mrp/README.rst b/stock_available_mrp/README.rst index 99e7569b8..aa5a9efdb 100644 --- a/stock_available_mrp/README.rst +++ b/stock_available_mrp/README.rst @@ -1,30 +1,61 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +========================================================= Consider the production potential is available to promise ========================================================= -This module takes the potential quantities available for Products in account in +This module takes the potential quantities available for Products into 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 -============ +Usage +===== +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/153/8.0 + +Known issues / Roadmap +====================== + +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 + +As a consequence, and to avoid overestimating, **only the first level** of Bill of Materials is considered. +However Sets (a.k.a "phantom" BoMs) are taken into account: if a component must be replaced with a set, it's the stock of the set's product which will decide the potential. + +If a product has several variants, only the variant with the biggest potential will be taken into account when reporting the production potential. +For example, even if you actually have enough components to make 10 iPads 16Go AND 42 iPads 32Go, we'll consider that you can promise only 42 iPads. + +Removed features +---------------- +Previous versions of this module used to let programmers demand to get the potential quantity in an arbitrary Unit of Measure using the `context`. This feature was present in the standard computations too until v8.0, but it has been dropped from the standard from v8.0 on. +For the sake of consistency the potential quantity is now always reported in the product's main Unit of Measure too. Roadmap ------- +Possible improvements for future versions: +* take manufacturing delays into account: we should not promise goods to customers if they want them delivered earlier that we can make them +* Compute the quantity of finished product that can be made directly on each Bill of Material: this would be useful for production managers, and may make the computations faster by avoiding to compute the same BoM several times when several variants share the same BoM +* 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. -* 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. +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed `feedback +`_. Credits ======= @@ -33,16 +64,19 @@ Contributors ------------ * Loïc Bellier (Numérigraphe) * Lionel Sausin (Numérigraphe) +* many thanks to Graeme Gellatly for his advice and code review Maintainer ---------- -.. image:: http://odoo-community.org/logo.png +.. image:: https://odoo-community.org/logo.png :alt: Odoo Community Association - :target: http://odoo-community.org + :target: https://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. +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 index 302cef51c..0443836b4 100644 --- a/stock_available_mrp/__init__.py +++ b/stock_available_mrp/__init__.py @@ -1,21 +1,5 @@ # -*- 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 . -# -############################################################################## +# © 2014 Numérigraphe SARL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import product +from . import models diff --git a/stock_available_mrp/__openerp__.py b/stock_available_mrp/__openerp__.py index 7e00810e0..407449db7 100644 --- a/stock_available_mrp/__openerp__.py +++ b/stock_available_mrp/__openerp__.py @@ -1,35 +1,18 @@ # -*- 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 . -# -############################################################################## - +# © 2014 Numérigraphe SARL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '2.0', + 'version': '8.0.3.0.0', "author": u"Numérigraphe,Odoo Community Association (OCA)", 'category': 'Hidden', 'depends': ['stock_available', 'mrp'], 'data': [ - 'product_view.xml', + 'views/product_template_view.xml', ], - 'test': [ - 'test/potential_qty.yml', + 'demo': [ + 'demo/mrp_bom.yml', ], 'license': 'AGPL-3', - 'installable': False + 'installable': True, } diff --git a/stock_available_mrp/demo/mrp_bom.yml b/stock_available_mrp/demo/mrp_bom.yml new file mode 100644 index 000000000..4ecdb0b64 --- /dev/null +++ b/stock_available_mrp/demo/mrp_bom.yml @@ -0,0 +1,28 @@ +- Create a UoM in the category of PCE +- !record {model: product.uom, id: thousand}: + name: Thousand + factor: 0.001 + rounding: 0.001 + uom_type: bigger + category_id: product.product_uom_categ_unit + +- Add a BOM whereby 0.042K "RAM SR2" can be replaced with 13 dozens "HDD-SH1" + 8 CPUa8 with 50% efficiency. This lets us test UoM conversions for the finished product and the raw materials, as well as the unfolding of phantom BoMs +- !record {model: mrp.bom, id: sr2_from_hdd}: + name: RAM SR2 made from HDD-SH1 + product_id: product.product_product_14 + product_tmpl_id: product.product_product_14_product_template + product_uom: thousand + product_qty: 0.042 + type: phantom + sequence: -1 + product_efficiency: 0.5 +- !record {model: mrp.bom.line, id: sr2_from_hdd_line1}: + bom_id: sr2_from_hdd + product_id: product.product_product_18 + product_qty: 13 + product_uom: product.product_uom_dozen +- !record {model: mrp.bom.line, id: sr2_from_hdd_line2}: + bom_id: sr2_from_hdd + product_id: product.product_product_23 + product_qty: 8 + product_uom: product.product_uom_unit diff --git a/stock_available_mrp/i18n/fr.po b/stock_available_mrp/i18n/fr.po index 860df2cc3..b65816aea 100644 --- a/stock_available_mrp/i18n/fr.po +++ b/stock_available_mrp/i18n/fr.po @@ -29,6 +29,5 @@ 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." - +msgid "Quantity of this Product that could be produced using the materials already at hand." +msgstr "Quantité de cet article que l'on pourrait produire en utilisant les produits déjà disponibles." diff --git a/stock_available_mrp/i18n/stock_available_mrp.pot b/stock_available_mrp/i18n/stock_available_mrp.pot index b1cffcca2..ae3a04141 100644 --- a/stock_available_mrp/i18n/stock_available_mrp.pot +++ b/stock_available_mrp/i18n/stock_available_mrp.pot @@ -29,6 +29,5 @@ 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." +msgid "Quantity of this Product that could be produced using the materials already at hand." msgstr "" - diff --git a/stock_available_mrp/models/__init__.py b/stock_available_mrp/models/__init__.py new file mode 100644 index 000000000..defb121af --- /dev/null +++ b/stock_available_mrp/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# © 2014 Numérigraphe SARL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import product_product +from . import product_template diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py new file mode 100644 index 000000000..5e00e6e2e --- /dev/null +++ b/stock_available_mrp/models/product_product.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# © 2014 Numérigraphe SARL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from collections import Counter + +from openerp import models, fields, api +from openerp.addons import decimal_precision as dp + + +class ProductProduct(models.Model): + _inherit = 'product.product' + + potential_qty = fields.Float( + compute='_get_potential_qty', + 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.") + + @api.multi + @api.depends('potential_qty') + def _immediately_usable_qty(self): + """Add the potential quantity to the quantity available to promise. + + This is the same implementation as for templates.""" + super(ProductProduct, self)._immediately_usable_qty() + for product in self: + product.immediately_usable_qty += product.potential_qty + + @api.multi + def _get_potential_qty(self): + """Compute the potential qty based on the available components.""" + # Browse the BOMs as superuser to bypass access rights + bom_obj = self.env['mrp.bom'].sudo() + + for product in self: + bom_id = bom_obj._bom_find(product_id=product.id) + if not bom_id: + product.potential_qty = 0.0 + continue + + # Need by product (same product can be in many BOM lines/levels) + component_needs = Counter() + for component in bom_obj._bom_explode(bom_obj.browse(bom_id), + product, 1.0,)[0]: + component_needs += Counter( + {component['product_id']: component['product_qty']}) + if not component_needs: + # The BoM has no line we can use + product.potential_qty = 0.0 + continue + + # Find the lowest quantity we can make with the stock at hand + product.potential_qty = min( + [self.browse(component_id).qty_available // need + for component_id, need in component_needs.items()]) diff --git a/stock_available_mrp/models/product_template.py b/stock_available_mrp/models/product_template.py new file mode 100644 index 000000000..f87ef6901 --- /dev/null +++ b/stock_available_mrp/models/product_template.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# © 2014 Numérigraphe SARL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, fields, api +from openerp.addons import decimal_precision as dp + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + potential_qty = fields.Float( + compute='_get_potential_qty', + 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. " + "If the product has several variants, this will be the biggest " + "quantity that can be made for a any single variant.") + + @api.multi + @api.depends('potential_qty') + def _immediately_usable_qty(self): + """Add the potential quantity to the quantity available to promise. + + This is the same implementation as for variants.""" + super(ProductTemplate, self)._immediately_usable_qty() + for tmpl in self: + tmpl.immediately_usable_qty += tmpl.potential_qty + + @api.multi + @api.depends('product_variant_ids.potential_qty') + def _get_potential_qty(self): + """Compute the potential as the max of all the variants's potential. + + We can't add the potential of variants: if they share components we + may not be able to make all the variants. + So we set the arbitrary rule that we can promise up to the biggest + variant's potential. + """ + for tmpl in self: + if not tmpl.product_variant_ids: + continue + tmpl.potential_qty = max( + [v.potential_qty for v in tmpl.product_variant_ids]) diff --git a/stock_available_mrp/product.py b/stock_available_mrp/product.py deleted file mode 100644 index f36b86a5c..000000000 --- a/stock_available_mrp/product.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- 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 deleted file mode 100644 index 707df74ff..000000000 --- a/stock_available_mrp/product_view.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - 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 deleted file mode 100644 index 352254900..000000000 --- a/stock_available_mrp/test/potential_qty.yml +++ /dev/null @@ -1,70 +0,0 @@ -- 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) diff --git a/stock_available_mrp/tests/__init__.py b/stock_available_mrp/tests/__init__.py new file mode 100644 index 000000000..e6b40413f --- /dev/null +++ b/stock_available_mrp/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2014 Numérigraphe SARL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_potential_qty diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py new file mode 100644 index 000000000..da7dd0670 --- /dev/null +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +# © 2014 Numérigraphe SARL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp.tests.common import TransactionCase + + +class TestPotentialQty(TransactionCase): + """Test the potential quantity on a product with a multi-line BoM""" + + def setUp(self): + super(TestPotentialQty, self).setUp() + + #  An interesting product (multi-line BoM, variants) + self.tmpl = self.browse_ref( + 'product.product_product_4_product_template') + #  First variant + self.var1 = self.browse_ref('product.product_product_4c') + #  Second variant + self.var2 = self.browse_ref('product.product_product_4') + # Components that can be used to make the product + component_ids = [ + # CPUa8 + self.ref('product.product_product_23'), + # RAM-SR2 + self.ref('product.product_product_14'), + # HDD SH-2 replaces RAM-SR2 through our demo phantom BoM + self.ref('product.product_product_18'), + # RAM-SR3 + self.ref('product.product_product_15')] + + # Zero-out the inventory of all variants and components + for component_id in ( + component_ids + [v.id + for v in self.tmpl.product_variant_ids]): + inventory = self.env['stock.inventory'].create( + {'name': 'no components: %s' % component_id, + 'location_id': self.ref('stock.stock_location_locations'), + 'filter': 'product', + 'product_id': component_id, + }) + inventory.prepare_inventory() + inventory.reset_real_qty() + inventory.action_done() + + #  A product without a BoM + self.product_wo_bom = self.browse_ref('product.product_product_23') + + # Record the initial quantity available for sale + self.initial_usable_qties = {i.id: i.immediately_usable_qty + for i in [self.tmpl, + self.var1, + self.var2, + self.product_wo_bom]} + + # Get the warehouses + self.wh_main = self.browse_ref('stock.warehouse0') + self.wh_ch = self.browse_ref('stock.stock_warehouse_shop0') + + def assertPotentialQty(self, record, qty, msg): + record.refresh() + # Check the potential + self.assertEqual(record.potential_qty, qty, msg) + # Check the variation of quantity available for sale + self.assertEqual( + (record.immediately_usable_qty - + self.initial_usable_qties[record.id]), qty, msg) + + def test_potential_qty_no_bom(self): + #  Check the potential when there's no BoM + self.assertPotentialQty( + self.product_wo_bom, 0.0, + "The potential without a BoM should be 0") + + def test_potential_qty(self): + for i in [self.tmpl, self.var1, self.var2]: + self.assertPotentialQty( + i, 0.0, + "The potential quantity should start at 0") + + # Receive 1000x CPUa8s + inventory = self.env['stock.inventory'].create( + {'name': 'Receive CPUa8', + 'location_id': self.wh_main.lot_stock_id.id, + 'filter': 'none'}) + inventory.prepare_inventory() + self.env['stock.inventory.line'].create({ + 'inventory_id': inventory.id, + 'product_id': self.ref('product.product_product_23'), + 'location_id': self.wh_main.lot_stock_id.id, + 'product_qty': 1000.0}) + inventory.action_done() + for i in [self.tmpl, self.var1, self.var2]: + self.assertPotentialQty( + i, 0.0, + "Receiving a single component should not change the " + "potential of %s" % i) + + # Receive enough RAM-SR3 to make 1000x the 1st variant in main WH + inventory = self.env['stock.inventory'].create( + {'name': 'components for 1st variant', + 'location_id': self.wh_main.lot_stock_id.id, + 'filter': 'none'}) + inventory.prepare_inventory() + self.env['stock.inventory.line'].create({ + 'inventory_id': inventory.id, + 'product_id': self.ref('product.product_product_15'), + 'location_id': self.wh_main.lot_stock_id.id, + 'product_qty': 1000.0}) + inventory.action_done() + self.assertPotentialQty( + self.tmpl, 1000.0, + "Wrong template potential after receiving components") + self.assertPotentialQty( + self.var1, 1000.0, + "Wrong variant 1 potential after receiving components") + self.assertPotentialQty( + self.var2, 0.0, + "Receiving variant 1's component should not change " + "variant 2's potential") + + # Receive enough components to make 500x the 2nd variant at Chicago + inventory = self.env['stock.inventory'].create( + {'name': 'components for 2nd variant', + 'location_id': self.wh_ch.lot_stock_id.id, + 'filter': 'none'}) + inventory.prepare_inventory() + self.env['stock.inventory.line'].create({ + 'inventory_id': inventory.id, + 'product_id': self.ref('product.product_product_23'), + 'location_id': self.wh_ch.lot_stock_id.id, + 'product_qty': 1000.0}) + self.env['stock.inventory.line'].create({ + 'inventory_id': inventory.id, + 'product_id': self.ref('product.product_product_18'), + 'location_id': self.wh_ch.lot_stock_id.id, + 'product_qty': 310.0}) + inventory.action_done() + self.assertPotentialQty( + self.tmpl, 1000.0, + "Wrong template potential after receiving components") + self.assertPotentialQty( + self.var1, 1000.0, + "Receiving variant 2's component should not change " + "variant 1's potential") + self.assertPotentialQty( + self.var2, 500.0, + "Wrong variant 2 potential after receiving components") + # Check by warehouse + self.assertPotentialQty( + self.tmpl.with_context(warehouse=self.wh_main.id), 1000.0, + "Wrong potential quantity in main WH") + self.assertPotentialQty( + self.tmpl.with_context(warehouse=self.wh_ch.id), 500.0, + "Wrong potential quantity in Chicago WH") + # Check by location + self.assertPotentialQty( + self.tmpl.with_context( + location=self.wh_main.lot_stock_id.id), 1000.0, + "Wrong potential quantity in main WH location") + self.assertPotentialQty( + self.tmpl.with_context( + location=self.wh_ch.lot_stock_id.id), + 500.0, + "Wrong potential quantity in Chicago WH location") diff --git a/stock_available_mrp/views/product_template_view.xml b/stock_available_mrp/views/product_template_view.xml new file mode 100644 index 000000000..c58512d56 --- /dev/null +++ b/stock_available_mrp/views/product_template_view.xml @@ -0,0 +1,19 @@ + + + + + + Potential quantity on product form + product.template + form + + + + + + + + + + + From 92de1c745515547fbb103bf26c6ad0c0447f4192 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Wed, 18 Nov 2015 18:23:32 +0100 Subject: [PATCH 03/29] [FIX] Browse mrp.bom as current user sudo is not required since mrp.bom are readable to groups with access to the qty_x fields on a product. Moreover using sudo to retrive the bom will ignore the company_id defined on the bom --- stock_available_mrp/README.rst | 1 + stock_available_mrp/__openerp__.py | 8 ++- stock_available_mrp/models/product_product.py | 3 +- .../tests/test_potential_qty.py | 64 +++++++++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/stock_available_mrp/README.rst b/stock_available_mrp/README.rst index aa5a9efdb..d32c27ae8 100644 --- a/stock_available_mrp/README.rst +++ b/stock_available_mrp/README.rst @@ -65,6 +65,7 @@ Contributors * Loïc Bellier (Numérigraphe) * Lionel Sausin (Numérigraphe) * many thanks to Graeme Gellatly for his advice and code review +* Laurent Mignon Maintainer ---------- diff --git a/stock_available_mrp/__openerp__.py b/stock_available_mrp/__openerp__.py index 407449db7..7523830a0 100644 --- a/stock_available_mrp/__openerp__.py +++ b/stock_available_mrp/__openerp__.py @@ -4,9 +4,13 @@ { 'name': 'Consider the production potential is available to promise', 'version': '8.0.3.0.0', - "author": u"Numérigraphe,Odoo Community Association (OCA)", + "author": u"Numérigraphe," + u"Odoo Community Association (OCA)", 'category': 'Hidden', - 'depends': ['stock_available', 'mrp'], + 'depends': [ + 'stock_available', + 'mrp' + ], 'data': [ 'views/product_template_view.xml', ], diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 5e00e6e2e..b486e3389 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -32,8 +32,7 @@ class ProductProduct(models.Model): @api.multi def _get_potential_qty(self): """Compute the potential qty based on the available components.""" - # Browse the BOMs as superuser to bypass access rights - bom_obj = self.env['mrp.bom'].sudo() + bom_obj = self.env['mrp.bom'] for product in self: bom_id = bom_obj._bom_find(product_id=product.id) diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index da7dd0670..a1321eb58 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -3,6 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from openerp.tests.common import TransactionCase +from openerp.osv.expression import TRUE_LEAF class TestPotentialQty(TransactionCase): @@ -72,6 +73,69 @@ class TestPotentialQty(TransactionCase): self.product_wo_bom, 0.0, "The potential without a BoM should be 0") + def test_potential_qty_no_bom_for_company(self): + # Receive 1000x CPUa8s + inventory = self.env['stock.inventory'].create( + {'name': 'Receive CPUa8', + 'location_id': self.wh_ch.lot_stock_id.id, + 'filter': 'none'}) + inventory.prepare_inventory() + self.env['stock.inventory.line'].create({ + 'inventory_id': inventory.id, + 'product_id': self.ref('product.product_product_23'), + 'location_id': self.wh_ch.lot_stock_id.id, + 'product_qty': 1000.0}) + inventory.action_done() + + # Receive enough RAM-SR3 to make 1000x the 1st variant in main WH + inventory = self.env['stock.inventory'].create( + {'name': 'components for 1st variant', + 'location_id': self.wh_ch.lot_stock_id.id, + 'filter': 'none'}) + inventory.prepare_inventory() + self.env['stock.inventory.line'].create({ + 'inventory_id': inventory.id, + 'product_id': self.ref('product.product_product_15'), + 'location_id': self.wh_ch.lot_stock_id.id, + 'product_qty': 1000.0}) + inventory.action_done() + self.assertPotentialQty( + self.tmpl, 1000.0, + "Wrong template potential after receiving components") + + test_user = self.env['res.users'].create({ + 'name': 'test_demo', + 'login': 'test_demo', + 'company_id': self.ref('base.main_company'), + 'company_ids': [(4, self.ref('base.main_company'))], + 'groups_id': [(4, self.ref('stock.group_stock_user'))]}) + + bom = self.env['mrp.bom'].search( + [('product_tmpl_id', '=', self.tmpl.id)]) + + test_user_tmpl = self.tmpl.sudo(test_user) + self.assertPotentialQty( + test_user_tmpl, 1000.0, + "Simple user can access to the potential_qty") + + # set the bom on the main company (visible to members of the main comp + # and all products without company (visible to all) + # and the demo user on Chicago (child of main company) + self.env['product.product'].search([ + TRUE_LEAF]).write({'company_id': False}) + chicago_id = self.ref('stock.res_company_1') + test_user.write({'company_id': chicago_id, + 'company_ids': [(4, chicago_id)]}) + test_user = test_user.sudo(test_user) + bom.company_id = self.ref('base.main_company') + self.assertPotentialQty( + test_user_tmpl, 0, + "The bom should not be visible to non members of the bom's " + "company or company child of the bom's company") + bom.company_id = chicago_id + self.assertPotentialQty( + test_user_tmpl, 1000.0, '') + def test_potential_qty(self): for i in [self.tmpl, self.var1, self.var2]: self.assertPotentialQty( From 09cf0cc3f2b4160564fa817d4377c686d3178980 Mon Sep 17 00:00:00 2001 From: Lionel Sausin Date: Wed, 13 Jan 2016 10:51:31 +0100 Subject: [PATCH 04/29] [FIX] fix multi-company test Record rules used to not be checked on stock quants, but now they are since Odoo's commit 2fd14db57433d08ab368a7e18d18ae8d49dafab1 (https://github.com/odoo/odoo/commit/2fd14db). In our test we changed the company of the products and BoMs but we neglected that the stock was not attached to the right company, and that made the test fail. To fix that, make the test inventory for the right company. Since there is a little inconsistency in the demo data with a negative quantity of an unrelated product, use the `partial` filter for the inventories instead of the `none` filter, so that no wrong inventory lines are added automatically. --- .../tests/test_potential_qty.py | 99 ++++++++++--------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index a1321eb58..b27203b9b 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -38,8 +38,7 @@ class TestPotentialQty(TransactionCase): {'name': 'no components: %s' % component_id, 'location_id': self.ref('stock.stock_location_locations'), 'filter': 'product', - 'product_id': component_id, - }) + 'product_id': component_id}) inventory.prepare_inventory() inventory.reset_real_qty() inventory.action_done() @@ -74,41 +73,47 @@ class TestPotentialQty(TransactionCase): "The potential without a BoM should be 0") def test_potential_qty_no_bom_for_company(self): - # Receive 1000x CPUa8s + chicago_id = self.ref('stock.res_company_1') + + # Receive 1000x CPUa8s owned by Chicago inventory = self.env['stock.inventory'].create( {'name': 'Receive CPUa8', + 'company_id': chicago_id, 'location_id': self.wh_ch.lot_stock_id.id, - 'filter': 'none'}) + 'filter': 'partial'}) inventory.prepare_inventory() - self.env['stock.inventory.line'].create({ - 'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_23'), - 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 1000.0}) + self.env['stock.inventory.line'].create( + {'inventory_id': inventory.id, + 'company_id': chicago_id, + 'product_id': self.ref('product.product_product_23'), + 'location_id': self.wh_ch.lot_stock_id.id, + 'product_qty': 1000.0}) inventory.action_done() - # Receive enough RAM-SR3 to make 1000x the 1st variant in main WH + # Put RAM-SR3 owned by Chicago for 1000x the 1st variant in main WH inventory = self.env['stock.inventory'].create( {'name': 'components for 1st variant', + 'company_id': chicago_id, 'location_id': self.wh_ch.lot_stock_id.id, - 'filter': 'none'}) + 'filter': 'partial'}) inventory.prepare_inventory() - self.env['stock.inventory.line'].create({ - 'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_15'), - 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 1000.0}) + self.env['stock.inventory.line'].create( + {'inventory_id': inventory.id, + 'company_id': chicago_id, + 'product_id': self.ref('product.product_product_15'), + 'location_id': self.wh_ch.lot_stock_id.id, + 'product_qty': 1000.0}) inventory.action_done() self.assertPotentialQty( self.tmpl, 1000.0, "Wrong template potential after receiving components") - test_user = self.env['res.users'].create({ - 'name': 'test_demo', - 'login': 'test_demo', - 'company_id': self.ref('base.main_company'), - 'company_ids': [(4, self.ref('base.main_company'))], - 'groups_id': [(4, self.ref('stock.group_stock_user'))]}) + test_user = self.env['res.users'].create( + {'name': 'test_demo', + 'login': 'test_demo', + 'company_id': self.ref('base.main_company'), + 'company_ids': [(4, self.ref('base.main_company'))], + 'groups_id': [(4, self.ref('stock.group_stock_user'))]}) bom = self.env['mrp.bom'].search( [('product_tmpl_id', '=', self.tmpl.id)]) @@ -118,15 +123,13 @@ class TestPotentialQty(TransactionCase): test_user_tmpl, 1000.0, "Simple user can access to the potential_qty") - # set the bom on the main company (visible to members of the main comp + # Set the bom on the main company (visible to members of main company) # and all products without company (visible to all) # and the demo user on Chicago (child of main company) self.env['product.product'].search([ TRUE_LEAF]).write({'company_id': False}) - chicago_id = self.ref('stock.res_company_1') test_user.write({'company_id': chicago_id, 'company_ids': [(4, chicago_id)]}) - test_user = test_user.sudo(test_user) bom.company_id = self.ref('base.main_company') self.assertPotentialQty( test_user_tmpl, 0, @@ -146,13 +149,13 @@ class TestPotentialQty(TransactionCase): inventory = self.env['stock.inventory'].create( {'name': 'Receive CPUa8', 'location_id': self.wh_main.lot_stock_id.id, - 'filter': 'none'}) + 'filter': 'partial'}) inventory.prepare_inventory() - self.env['stock.inventory.line'].create({ - 'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_23'), - 'location_id': self.wh_main.lot_stock_id.id, - 'product_qty': 1000.0}) + self.env['stock.inventory.line'].create( + {'inventory_id': inventory.id, + 'product_id': self.ref('product.product_product_23'), + 'location_id': self.wh_main.lot_stock_id.id, + 'product_qty': 1000.0}) inventory.action_done() for i in [self.tmpl, self.var1, self.var2]: self.assertPotentialQty( @@ -164,13 +167,13 @@ class TestPotentialQty(TransactionCase): inventory = self.env['stock.inventory'].create( {'name': 'components for 1st variant', 'location_id': self.wh_main.lot_stock_id.id, - 'filter': 'none'}) + 'filter': 'partial'}) inventory.prepare_inventory() - self.env['stock.inventory.line'].create({ - 'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_15'), - 'location_id': self.wh_main.lot_stock_id.id, - 'product_qty': 1000.0}) + self.env['stock.inventory.line'].create( + {'inventory_id': inventory.id, + 'product_id': self.ref('product.product_product_15'), + 'location_id': self.wh_main.lot_stock_id.id, + 'product_qty': 1000.0}) inventory.action_done() self.assertPotentialQty( self.tmpl, 1000.0, @@ -187,18 +190,18 @@ class TestPotentialQty(TransactionCase): inventory = self.env['stock.inventory'].create( {'name': 'components for 2nd variant', 'location_id': self.wh_ch.lot_stock_id.id, - 'filter': 'none'}) + 'filter': 'partial'}) inventory.prepare_inventory() - self.env['stock.inventory.line'].create({ - 'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_23'), - 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 1000.0}) - self.env['stock.inventory.line'].create({ - 'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_18'), - 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 310.0}) + self.env['stock.inventory.line'].create( + {'inventory_id': inventory.id, + 'product_id': self.ref('product.product_product_23'), + 'location_id': self.wh_ch.lot_stock_id.id, + 'product_qty': 1000.0}) + self.env['stock.inventory.line'].create( + {'inventory_id': inventory.id, + 'product_id': self.ref('product.product_product_18'), + 'location_id': self.wh_ch.lot_stock_id.id, + 'product_qty': 310.0}) inventory.action_done() self.assertPotentialQty( self.tmpl, 1000.0, From 93c1dc8f66399f46a49dd85a965a123670ac4bbb Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sun, 31 Jan 2016 00:24:23 -0500 Subject: [PATCH 05/29] OCA Transbot updated translations from Transifex --- stock_available_mrp/i18n/de.po | 34 ++++++++++++++++++++++++++++++++ stock_available_mrp/i18n/es.po | 33 +++++++++++++++++++++++++++++++ stock_available_mrp/i18n/fi.po | 33 +++++++++++++++++++++++++++++++ stock_available_mrp/i18n/fr.po | 36 +++++++++++++++++----------------- stock_available_mrp/i18n/sl.po | 34 ++++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 18 deletions(-) create mode 100644 stock_available_mrp/i18n/de.po create mode 100644 stock_available_mrp/i18n/es.po create mode 100644 stock_available_mrp/i18n/fi.po create mode 100644 stock_available_mrp/i18n/sl.po diff --git a/stock_available_mrp/i18n/de.po b/stock_available_mrp/i18n/de.po new file mode 100644 index 000000000..3fc625b7f --- /dev/null +++ b/stock_available_mrp/i18n/de.po @@ -0,0 +1,34 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_available_mrp +# +# Translators: +# Rudolf Schnapka , 2016 +msgid "" +msgstr "" +"Project-Id-Version: stock-logistics-warehouse (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-14 01:38+0000\n" +"PO-Revision-Date: 2016-01-14 09:35+0000\n" +"Last-Translator: Rudolf Schnapka \n" +"Language-Team: German (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_product +msgid "Product" +msgstr "Produkt" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_template +msgid "Product Template" +msgstr "Produktvorlage" + +#. module: stock_available_mrp +#: model:product.uom,name:stock_available_mrp.thousand +msgid "Thousand" +msgstr "Tausend" diff --git a/stock_available_mrp/i18n/es.po b/stock_available_mrp/i18n/es.po new file mode 100644 index 000000000..aab2eb182 --- /dev/null +++ b/stock_available_mrp/i18n/es.po @@ -0,0 +1,33 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_available_mrp +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: stock-logistics-warehouse (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-14 01:38+0000\n" +"PO-Revision-Date: 2016-01-13 16:35+0000\n" +"Last-Translator: <>\n" +"Language-Team: Spanish (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_product +msgid "Product" +msgstr "Producto" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_template +msgid "Product Template" +msgstr "Plantilla de producto" + +#. module: stock_available_mrp +#: model:product.uom,name:stock_available_mrp.thousand +msgid "Thousand" +msgstr "" diff --git a/stock_available_mrp/i18n/fi.po b/stock_available_mrp/i18n/fi.po new file mode 100644 index 000000000..fa81ddfe0 --- /dev/null +++ b/stock_available_mrp/i18n/fi.po @@ -0,0 +1,33 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_available_mrp +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: stock-logistics-warehouse (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-14 01:38+0000\n" +"PO-Revision-Date: 2016-01-13 16:35+0000\n" +"Last-Translator: <>\n" +"Language-Team: Finnish (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_product +msgid "Product" +msgstr "Tuote" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_template +msgid "Product Template" +msgstr "Tuotteen malli" + +#. module: stock_available_mrp +#: model:product.uom,name:stock_available_mrp.thousand +msgid "Thousand" +msgstr "" diff --git a/stock_available_mrp/i18n/fr.po b/stock_available_mrp/i18n/fr.po index b65816aea..0aaafddd3 100644 --- a/stock_available_mrp/i18n/fr.po +++ b/stock_available_mrp/i18n/fr.po @@ -1,33 +1,33 @@ -# Translation of OpenERP Server. +# Translation of Odoo Server. # This file contains the translation of the following modules: -# * stock_available_mrp -# +# * stock_available_mrp +# +# Translators: msgid "" msgstr "" -"Project-Id-Version: OpenERP Server 7.0\n" +"Project-Id-Version: stock-logistics-warehouse (8.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" +"POT-Creation-Date: 2016-01-14 01:38+0000\n" +"PO-Revision-Date: 2016-01-13 16:35+0000\n" "Last-Translator: <>\n" -"Language-Team: \n" +"Language-Team: French (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Plural-Forms: \n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\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." -msgstr "Quantité de cet article que l'on pourrait produire en utilisant les produits déjà disponibles." +#: model:ir.model,name:stock_available_mrp.model_product_template +msgid "Product Template" +msgstr "Modèle de produit" + +#. module: stock_available_mrp +#: model:product.uom,name:stock_available_mrp.thousand +msgid "Thousand" +msgstr "" diff --git a/stock_available_mrp/i18n/sl.po b/stock_available_mrp/i18n/sl.po new file mode 100644 index 000000000..753889eea --- /dev/null +++ b/stock_available_mrp/i18n/sl.po @@ -0,0 +1,34 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_available_mrp +# +# Translators: +# Matjaž Mozetič , 2016 +msgid "" +msgstr "" +"Project-Id-Version: stock-logistics-warehouse (8.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-14 01:38+0000\n" +"PO-Revision-Date: 2016-01-14 05:18+0000\n" +"Last-Translator: Matjaž Mozetič \n" +"Language-Team: Slovenian (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/sl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sl\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_product +msgid "Product" +msgstr "Proizvod" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_template +msgid "Product Template" +msgstr "Predloga proizvoda" + +#. module: stock_available_mrp +#: model:product.uom,name:stock_available_mrp.thousand +msgid "Thousand" +msgstr "Tisoč" From 3801d3481c56674731ff656bdd1654d93a655ffe Mon Sep 17 00:00:00 2001 From: Cyril Gaudin Date: Tue, 15 Mar 2016 14:08:31 +0100 Subject: [PATCH 06/29] stock_available_mrp: fix BOM qty (multi-units, efficiency) --- stock_available_mrp/__openerp__.py | 2 +- stock_available_mrp/models/product_product.py | 56 +++++- .../tests/test_potential_qty.py | 174 +++++++++++++++++- 3 files changed, 216 insertions(+), 16 deletions(-) diff --git a/stock_available_mrp/__openerp__.py b/stock_available_mrp/__openerp__.py index 7523830a0..0e10d6a6e 100644 --- a/stock_available_mrp/__openerp__.py +++ b/stock_available_mrp/__openerp__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '8.0.3.0.0', + 'version': '8.0.3.0.1', "author": u"Numérigraphe," u"Odoo Community Association (OCA)", 'category': 'Hidden', diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index b486e3389..b9ade817e 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -33,6 +33,7 @@ class ProductProduct(models.Model): def _get_potential_qty(self): """Compute the potential qty based on the available components.""" bom_obj = self.env['mrp.bom'] + uom_obj = self.env['product.uom'] for product in self: bom_id = bom_obj._bom_find(product_id=product.id) @@ -40,18 +41,53 @@ class ProductProduct(models.Model): product.potential_qty = 0.0 continue + bom = bom_obj.browse(bom_id) + # Need by product (same product can be in many BOM lines/levels) - component_needs = Counter() - for component in bom_obj._bom_explode(bom_obj.browse(bom_id), - product, 1.0,)[0]: - component_needs += Counter( - {component['product_id']: component['product_qty']}) + component_needs = self._get_components_needs(product, bom) + if not component_needs: # The BoM has no line we can use product.potential_qty = 0.0 - continue - # Find the lowest quantity we can make with the stock at hand - product.potential_qty = min( - [self.browse(component_id).qty_available // need - for component_id, need in component_needs.items()]) + else: + # Find the lowest quantity we can make with the stock at hand + components_potential_qty = min( + [component.qty_available // need + for component, need in component_needs.items()] + ) + + # Compute with bom quantity + bom_qty = uom_obj._compute_qty_obj( + bom.product_uom, + bom.product_qty, + bom.product_tmpl_id.uom_id + ) + product.potential_qty = bom_qty * components_potential_qty + + def _get_components_needs(self, product, bom): + """ Return the needed qty of each compoments in the *bom* of *product*. + + :type product: product_product + :type bom: mrp_bom + :rtype: collections.Counter + """ + bom_obj = self.env['mrp.bom'] + uom_obj = self.env['product.uom'] + product_obj = self.env['product.product'] + + needs = Counter() + for bom_component in bom_obj._bom_explode(bom, product, 1.0)[0]: + product_uom = uom_obj.browse(bom_component['product_uom']) + component = product_obj.browse(bom_component['product_id']) + + component_qty = uom_obj._compute_qty_obj( + product_uom, + bom_component['product_qty'], + component.uom_id, + ) + needs += Counter( + {component: component_qty} + ) + + return needs diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index b27203b9b..b3d6e09de 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -12,6 +12,14 @@ class TestPotentialQty(TransactionCase): def setUp(self): super(TestPotentialQty, self).setUp() + self.product_model = self.env["product.product"] + self.bom_model = self.env["mrp.bom"] + self.bom_line_model = self.env["mrp.bom.line"] + self.stock_quant_model = self.env["stock.quant"] + + self.setup_demo_data() + + def setup_demo_data(self): #  An interesting product (multi-line BoM, variants) self.tmpl = self.browse_ref( 'product.product_product_4_product_template') @@ -57,6 +65,25 @@ class TestPotentialQty(TransactionCase): self.wh_main = self.browse_ref('stock.warehouse0') self.wh_ch = self.browse_ref('stock.stock_warehouse_shop0') + def create_inventory(self, product_id, qty, location_id=None): + if location_id is None: + location_id = self.wh_main.lot_stock_id.id + + inventory = self.env['stock.inventory'].create({ + 'name': 'Test inventory', + 'location_id': location_id, + 'filter': 'partial' + }) + inventory.prepare_inventory() + + self.env['stock.inventory.line'].create({ + 'inventory_id': inventory.id, + 'product_id': product_id, + 'location_id': location_id, + 'product_qty': qty + }) + inventory.action_done() + def assertPotentialQty(self, record, qty, msg): record.refresh() # Check the potential @@ -186,7 +213,9 @@ class TestPotentialQty(TransactionCase): "Receiving variant 1's component should not change " "variant 2's potential") - # Receive enough components to make 500x the 2nd variant at Chicago + # Receive enough components to make 42X the 2nd variant at Chicago + # need 13 dozens of HDD with 50% efficiency to build 42 RAM + # So 313 HDD (with rounding) for 42 RAM inventory = self.env['stock.inventory'].create( {'name': 'components for 2nd variant', 'location_id': self.wh_ch.lot_stock_id.id, @@ -201,7 +230,7 @@ class TestPotentialQty(TransactionCase): {'inventory_id': inventory.id, 'product_id': self.ref('product.product_product_18'), 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 310.0}) + 'product_qty': 313.0}) inventory.action_done() self.assertPotentialQty( self.tmpl, 1000.0, @@ -211,14 +240,14 @@ class TestPotentialQty(TransactionCase): "Receiving variant 2's component should not change " "variant 1's potential") self.assertPotentialQty( - self.var2, 500.0, + self.var2, 42.0, "Wrong variant 2 potential after receiving components") # Check by warehouse self.assertPotentialQty( self.tmpl.with_context(warehouse=self.wh_main.id), 1000.0, "Wrong potential quantity in main WH") self.assertPotentialQty( - self.tmpl.with_context(warehouse=self.wh_ch.id), 500.0, + self.tmpl.with_context(warehouse=self.wh_ch.id), 42.0, "Wrong potential quantity in Chicago WH") # Check by location self.assertPotentialQty( @@ -228,5 +257,140 @@ class TestPotentialQty(TransactionCase): self.assertPotentialQty( self.tmpl.with_context( location=self.wh_ch.lot_stock_id.id), - 500.0, + 42.0, "Wrong potential quantity in Chicago WH location") + + def test_multi_unit_recursive_bom(self): + # Test multi-level and multi-units BOM + + p1 = self.product_model.create({ + 'name': 'Test product with BOM', + }) + + p2 = self.product_model.create({ + 'name': 'Test sub product with BOM', + }) + + p3 = self.product_model.create({ + 'name': 'Test component' + }) + + bom_p1 = self.bom_model.create({ + 'product_tmpl_id': p1.product_tmpl_id.id, + 'product_id': p1.id, + }) + + # 1 dozen of component + self.bom_line_model.create({ + 'bom_id': bom_p1.id, + 'product_id': p3.id, + 'product_qty': 1, + 'product_uom': self.ref('product.product_uom_dozen'), + }) + + # Two p2 which have a bom + self.bom_line_model.create({ + 'bom_id': bom_p1.id, + 'product_id': p2.id, + 'product_qty': 2, + 'product_uom': self.ref('product.product_uom_unit'), + 'type': 'phantom', + }) + + bom_p2 = self.bom_model.create({ + 'product_tmpl_id': p2.product_tmpl_id.id, + 'product_id': p2.id, + }) + + # p2 need 2 unit of component + self.bom_line_model.create({ + 'bom_id': bom_p2.id, + 'product_id': p3.id, + 'product_qty': 2, + 'product_uom': self.ref('product.product_uom_unit'), + }) + + p1.refresh() + + # Need a least 1 dozen + 2 * 2 = 16 units for one P1 + self.assertEqual(0, p1.potential_qty) + + self.create_inventory(p3.id, 1) + + p1.refresh() + self.assertEqual(0, p1.potential_qty) + + self.create_inventory(p3.id, 15) + p1.refresh() + self.assertEqual(0, p1.potential_qty) + + self.create_inventory(p3.id, 16) + p1.refresh() + self.assertEqual(1.0, p1.potential_qty) + + self.create_inventory(p3.id, 25) + p1.refresh() + self.assertEqual(1.0, p1.potential_qty) + + self.create_inventory(p3.id, 32) + p1.refresh() + self.assertEqual(2.0, p1.potential_qty) + + def test_bom_qty_and_efficiency(self): + + p1 = self.product_model.create({ + 'name': 'Test product with BOM', + }) + + p2 = self.product_model.create({ + 'name': 'Test sub product with BOM', + }) + + p3 = self.product_model.create({ + 'name': 'Test component' + }) + + # A bom produce 2 dozen of P1 + bom_p1 = self.bom_model.create({ + 'product_tmpl_id': p1.product_tmpl_id.id, + 'product_id': p1.id, + 'product_qty': 2, + 'product_uom': self.ref('product.product_uom_dozen'), + }) + + # Need 5 p2 for that + self.bom_line_model.create({ + 'bom_id': bom_p1.id, + 'product_id': p2.id, + 'product_qty': 5, + 'product_uom': self.ref('product.product_uom_unit'), + 'product_efficiency': 0.8, + }) + + # Which need 1 dozen of P3 + bom_p2 = self.bom_model.create({ + 'product_tmpl_id': p2.product_tmpl_id.id, + 'product_id': p2.id, + 'type': 'phantom', + }) + self.bom_line_model.create({ + 'bom_id': bom_p2.id, + 'product_id': p3.id, + 'product_qty': 1, + 'product_uom': self.ref('product.product_uom_dozen'), + }) + + p1.refresh() + self.assertEqual(0, p1.potential_qty) + + self.create_inventory(p3.id, 60) + + p1.refresh() + self.assertEqual(0, p1.potential_qty) + + # Need 5 * 1 dozen => 60 + # But 80% lost each dozen, need 3 more by dozen => 60 + 5 *3 => 75 + self.create_inventory(p3.id, 75) + + p1.refresh() + self.assertEqual(24, p1.potential_qty) From fb41ccd372d9b48234ae47d7e610eb514a6c20f4 Mon Sep 17 00:00:00 2001 From: Laetitia Gangloff Date: Fri, 4 Mar 2016 11:03:16 +0100 Subject: [PATCH 07/29] stock_available / stock_available_mrp : add possibility to choose which field use to compute potential --- stock_available_mrp/README.rst | 3 +++ stock_available_mrp/__openerp__.py | 2 +- stock_available_mrp/models/product_product.py | 9 ++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/stock_available_mrp/README.rst b/stock_available_mrp/README.rst index d32c27ae8..b47d8dbf0 100644 --- a/stock_available_mrp/README.rst +++ b/stock_available_mrp/README.rst @@ -9,6 +9,9 @@ Consider the production potential is available to promise This module takes the potential quantities available for Products into account in the quantity available to promise, where the "Potential quantity" is the quantity that can be manufactured with the components immediately at hand. +By configuration, the "Potential quantity" can be computed based on other product field. +For example, "Potential quantity" can be the quantity that can be manufactured +with the components available to promise. Usage ===== diff --git a/stock_available_mrp/__openerp__.py b/stock_available_mrp/__openerp__.py index 0e10d6a6e..64ad57aaa 100644 --- a/stock_available_mrp/__openerp__.py +++ b/stock_available_mrp/__openerp__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '8.0.3.0.1', + 'version': '8.0.3.1.0', "author": u"Numérigraphe," u"Odoo Community Association (OCA)", 'category': 'Hidden', diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index b9ade817e..91daaab75 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -6,6 +6,7 @@ from collections import Counter from openerp import models, fields, api from openerp.addons import decimal_precision as dp +from openerp.tools.safe_eval import safe_eval class ProductProduct(models.Model): @@ -35,6 +36,12 @@ class ProductProduct(models.Model): bom_obj = self.env['mrp.bom'] uom_obj = self.env['product.uom'] + icp = self.env['ir.config_parameter'] + stock_available_mrp_based_on = safe_eval( + icp.get_param('stock_available_mrp_based_on', 'False')) + if not stock_available_mrp_based_on: + stock_available_mrp_based_on = 'qty_available' + for product in self: bom_id = bom_obj._bom_find(product_id=product.id) if not bom_id: @@ -53,7 +60,7 @@ class ProductProduct(models.Model): else: # Find the lowest quantity we can make with the stock at hand components_potential_qty = min( - [component.qty_available // need + [getattr(component, stock_available_mrp_based_on) // need for component, need in component_needs.items()] ) From 4d2b9567e7dbc9dfa6a9b302572f6cd7691aa9b1 Mon Sep 17 00:00:00 2001 From: Cyril Gaudin Date: Wed, 16 Mar 2016 13:06:46 +0100 Subject: [PATCH 08/29] [FIX+IMP] stock_available_mrp: fix recursive potential_qty in list view + small improvements --- stock_available_mrp/__openerp__.py | 2 +- stock_available_mrp/models/product_product.py | 43 +++++-- .../tests/test_potential_qty.py | 119 ++++++++++++++++++ 3 files changed, 155 insertions(+), 9 deletions(-) diff --git a/stock_available_mrp/__openerp__.py b/stock_available_mrp/__openerp__.py index 64ad57aaa..2d6afa640 100644 --- a/stock_available_mrp/__openerp__.py +++ b/stock_available_mrp/__openerp__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '8.0.3.1.0', + 'version': '8.0.3.1.1', "author": u"Numérigraphe," u"Odoo Community Association (OCA)", 'category': 'Hidden', diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 91daaab75..c6440f31a 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -6,7 +6,6 @@ from collections import Counter from openerp import models, fields, api from openerp.addons import decimal_precision as dp -from openerp.tools.safe_eval import safe_eval class ProductProduct(models.Model): @@ -20,6 +19,14 @@ class ProductProduct(models.Model): help="Quantity of this Product that could be produced using " "the materials already at hand.") + # Needed for fields dependencies + # When self.potential_qty is compute, we want to force the ORM + # to compute all the components potential_qty too. + component_ids = fields.Many2many( + comodel_name='product.product', + compute='_get_component_ids', + ) + @api.multi @api.depends('potential_qty') def _immediately_usable_qty(self): @@ -31,17 +38,12 @@ class ProductProduct(models.Model): product.immediately_usable_qty += product.potential_qty @api.multi + @api.depends('component_ids.potential_qty') def _get_potential_qty(self): """Compute the potential qty based on the available components.""" bom_obj = self.env['mrp.bom'] uom_obj = self.env['product.uom'] - icp = self.env['ir.config_parameter'] - stock_available_mrp_based_on = safe_eval( - icp.get_param('stock_available_mrp_based_on', 'False')) - if not stock_available_mrp_based_on: - stock_available_mrp_based_on = 'qty_available' - for product in self: bom_id = bom_obj._bom_find(product_id=product.id) if not bom_id: @@ -60,7 +62,7 @@ class ProductProduct(models.Model): else: # Find the lowest quantity we can make with the stock at hand components_potential_qty = min( - [getattr(component, stock_available_mrp_based_on) // need + [self._get_component_qty(component) // need for component, need in component_needs.items()] ) @@ -72,6 +74,19 @@ class ProductProduct(models.Model): ) product.potential_qty = bom_qty * components_potential_qty + def _get_component_qty(self, component): + """ Return the component qty to use based en company settings. + + :type component: product_product + :rtype: float + """ + icp = self.env['ir.config_parameter'] + stock_available_mrp_based_on = icp.get_param( + 'stock_available_mrp_based_on', 'qty_available' + ) + + return component[stock_available_mrp_based_on] + def _get_components_needs(self, product, bom): """ Return the needed qty of each compoments in the *bom* of *product*. @@ -98,3 +113,15 @@ class ProductProduct(models.Model): ) return needs + + def _get_component_ids(self): + """ Compute component_ids by getting all the components for + this product. + """ + bom_obj = self.env['mrp.bom'] + + bom_id = bom_obj._bom_find(product_id=self.id) + if bom_id: + bom = bom_obj.browse(bom_id) + for bom_component in bom_obj._bom_explode(bom, self, 1.0)[0]: + self.component_ids |= self.browse(bom_component['product_id']) diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index b3d6e09de..98eb9a545 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -16,6 +16,7 @@ class TestPotentialQty(TransactionCase): self.bom_model = self.env["mrp.bom"] self.bom_line_model = self.env["mrp.bom.line"] self.stock_quant_model = self.env["stock.quant"] + self.config = self.env['ir.config_parameter'] self.setup_demo_data() @@ -84,6 +85,24 @@ class TestPotentialQty(TransactionCase): }) inventory.action_done() + def create_simple_bom(self, product, sub_product, + product_qty=1, sub_product_qty=1): + bom = self.bom_model.create({ + 'product_tmpl_id': product.product_tmpl_id.id, + 'product_id': product.id, + 'product_qty': product_qty, + 'product_uom': self.ref('product.product_uom_unit'), + + }) + self.bom_line_model.create({ + 'bom_id': bom.id, + 'product_id': sub_product.id, + 'product_qty': sub_product_qty, + 'product_uom': self.ref('product.product_uom_unit'), + }) + + return bom + def assertPotentialQty(self, record, qty, msg): record.refresh() # Check the potential @@ -394,3 +413,103 @@ class TestPotentialQty(TransactionCase): p1.refresh() self.assertEqual(24, p1.potential_qty) + + def test_component_stock_choice(self): + # Test to change component stock for compute BOM stock + + # Get a demo product with outgoing move (qty: 3) + imac = self.browse_ref('product.product_product_8') + + # Set on hand qty + self.create_inventory(imac.id, 3) + + # Create a product with BOM + p1 = self.product_model.create({ + 'name': 'Test product with BOM', + }) + bom_p1 = self.bom_model.create({ + 'product_tmpl_id': p1.product_tmpl_id.id, + 'product_id': p1.id, + 'product_qty': 1, + 'product_uom': self.ref('product.product_uom_unit'), + }) + + # Need 1 iMac for that + p1_bom_line = self.bom_line_model.create({ + 'bom_id': bom_p1.id, + 'product_id': imac.id, + 'product_qty': 1, + 'product_uom': self.ref('product.product_uom_unit'), + }) + + # Default component is qty_available + p1.refresh() + self.assertEqual(3.0, p1.potential_qty) + + # Change to immediately usable + self.config.set_param('stock_available_mrp_based_on', + 'immediately_usable_qty') + + p1.refresh() + self.assertEqual(0.0, p1.potential_qty) + + # If iMac has a Bom and can be manufactured + imac_component = self.product_model.create({ + 'name': 'iMac component', + }) + self.create_inventory(imac_component.id, 5) + + imac_bom = self.bom_model.create({ + 'product_tmpl_id': imac.product_tmpl_id.id, + 'product_id': imac.id, + 'product_qty': 1, + 'product_uom': self.ref('product.product_uom_unit'), + }) + p1_bom_line.type = 'phantom' + + # Need 1 imac_component for iMac + self.bom_line_model.create({ + 'bom_id': imac_bom.id, + 'product_id': imac_component.id, + 'product_qty': 1, + 'product_uom': self.ref('product.product_uom_unit'), + }) + + p1.refresh() + self.assertEqual(5.0, p1.potential_qty) + + # Changing to virtual (same as immediately in current config) + self.config.set_param('stock_available_mrp_based_on', + 'virtual_available') + p1.refresh() + self.assertEqual(5.0, p1.potential_qty) + + def test_potential_qty__list(self): + # Try to highlight a bug when _get_potential_qty is called on + # a recordset with multiple products + # Recursive compute is not working + + p1 = self.product_model.create({'name': 'Test P1'}) + p2 = self.product_model.create({'name': 'Test P2'}) + p3 = self.product_model.create({'name': 'Test P3'}) + + self.config.set_param('stock_available_mrp_based_on', + 'immediately_usable_qty') + + # P1 need one P2 + self.create_simple_bom(p1, p2) + # P2 need one P3 + self.create_simple_bom(p2, p3) + + self.create_inventory(p3.id, 3) + + self.product_model.invalidate_cache() + + products = self.product_model.search( + [('id', 'in', [p1.id, p2.id, p3.id])] + ) + + self.assertEqual( + {p1.id: 3.0, p2.id: 3.0, p3.id: 0.0}, + {p.id: p.potential_qty for p in products} + ) From c57208670acd789607dd17cdf73ac57c40ffdb14 Mon Sep 17 00:00:00 2001 From: Cyril Gaudin Date: Mon, 21 Mar 2016 10:45:31 +0100 Subject: [PATCH 09/29] V9 migration modifications. * mrp_bom.name has been deleted. * mrp_bom_line.type moved to mrp_bom.type. * Fix missing group_mrp_user issue. * Change versions --- stock_available_mrp/__openerp__.py | 4 +- stock_available_mrp/demo/mrp_bom.yml | 1 - stock_available_mrp/models/product_product.py | 9 ++++- .../tests/test_potential_qty.py | 37 ++++++++++++++++--- .../views/product_template_view.xml | 12 +++++- 5 files changed, 51 insertions(+), 12 deletions(-) diff --git a/stock_available_mrp/__openerp__.py b/stock_available_mrp/__openerp__.py index 2d6afa640..0aa748b7e 100644 --- a/stock_available_mrp/__openerp__.py +++ b/stock_available_mrp/__openerp__.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# © 2014 Numérigraphe SARL +# © 2014 Numérigraphe SARL, Camptocamp # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '8.0.3.1.1', + 'version': '9.0.1.0.0', "author": u"Numérigraphe," u"Odoo Community Association (OCA)", 'category': 'Hidden', diff --git a/stock_available_mrp/demo/mrp_bom.yml b/stock_available_mrp/demo/mrp_bom.yml index 4ecdb0b64..bb197f44b 100644 --- a/stock_available_mrp/demo/mrp_bom.yml +++ b/stock_available_mrp/demo/mrp_bom.yml @@ -8,7 +8,6 @@ - Add a BOM whereby 0.042K "RAM SR2" can be replaced with 13 dozens "HDD-SH1" + 8 CPUa8 with 50% efficiency. This lets us test UoM conversions for the finished product and the raw materials, as well as the unfolding of phantom BoMs - !record {model: mrp.bom, id: sr2_from_hdd}: - name: RAM SR2 made from HDD-SH1 product_id: product.product_product_14 product_tmpl_id: product.product_product_14_product_template product_uom: thousand diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index c6440f31a..790ca16a4 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -7,6 +7,8 @@ from collections import Counter from openerp import models, fields, api from openerp.addons import decimal_precision as dp +from openerp.exceptions import AccessError + class ProductProduct(models.Model): _inherit = 'product.product' @@ -53,7 +55,12 @@ class ProductProduct(models.Model): bom = bom_obj.browse(bom_id) # Need by product (same product can be in many BOM lines/levels) - component_needs = self._get_components_needs(product, bom) + try: + component_needs = self._get_components_needs(product, bom) + except AccessError: + # If user doesn't have access to BOM + # he can't see potential_qty + component_needs = None if not component_needs: # The BoM has no line we can use diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index 98eb9a545..896655dae 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -86,13 +86,14 @@ class TestPotentialQty(TransactionCase): inventory.action_done() def create_simple_bom(self, product, sub_product, - product_qty=1, sub_product_qty=1): + product_qty=1, sub_product_qty=1, + routing_id=False): bom = self.bom_model.create({ 'product_tmpl_id': product.product_tmpl_id.id, 'product_id': product.id, 'product_qty': product_qty, 'product_uom': self.ref('product.product_uom_unit'), - + 'routing_id': routing_id, }) self.bom_line_model.create({ 'bom_id': bom.id, @@ -159,7 +160,8 @@ class TestPotentialQty(TransactionCase): 'login': 'test_demo', 'company_id': self.ref('base.main_company'), 'company_ids': [(4, self.ref('base.main_company'))], - 'groups_id': [(4, self.ref('stock.group_stock_user'))]}) + 'groups_id': [(4, self.ref('stock.group_stock_user')), + (4, self.ref('mrp.group_mrp_user'))]}) bom = self.env['mrp.bom'].search( [('product_tmpl_id', '=', self.tmpl.id)]) @@ -185,6 +187,29 @@ class TestPotentialQty(TransactionCase): self.assertPotentialQty( test_user_tmpl, 1000.0, '') + def test_group_mrp_missing(self): + test_user = self.env['res.users'].create({ + 'name': 'test_demo', + 'login': 'test_demo', + 'company_id': self.ref('base.main_company'), + 'company_ids': [(4, self.ref('base.main_company'))], + 'groups_id': [(4, self.ref('stock.group_stock_user'))], + }) + + p1 = self.product_model.create({'name': 'Test P1'}) + p2 = self.product_model.create({'name': 'Test P2'}) + + self.create_simple_bom(p1, p2, + routing_id=self.ref('mrp.mrp_routing_0')) + self.create_inventory(p2.id, 1) + + test_user_p1 = p1.sudo(test_user) + # Test user doesn't have access to mrp_routing, can't compute potential + self.assertEqual(0, test_user_p1.potential_qty) + + test_user.groups_id = [(4, self.ref('mrp.group_mrp_user'))] + self.assertEqual(1, test_user_p1.potential_qty) + def test_potential_qty(self): for i in [self.tmpl, self.var1, self.var2]: self.assertPotentialQty( @@ -313,12 +338,12 @@ class TestPotentialQty(TransactionCase): 'product_id': p2.id, 'product_qty': 2, 'product_uom': self.ref('product.product_uom_unit'), - 'type': 'phantom', }) bom_p2 = self.bom_model.create({ 'product_tmpl_id': p2.product_tmpl_id.id, 'product_id': p2.id, + 'type': 'phantom', }) # p2 need 2 unit of component @@ -435,7 +460,7 @@ class TestPotentialQty(TransactionCase): }) # Need 1 iMac for that - p1_bom_line = self.bom_line_model.create({ + self.bom_line_model.create({ 'bom_id': bom_p1.id, 'product_id': imac.id, 'product_qty': 1, @@ -464,8 +489,8 @@ class TestPotentialQty(TransactionCase): 'product_id': imac.id, 'product_qty': 1, 'product_uom': self.ref('product.product_uom_unit'), + 'type': 'phantom', }) - p1_bom_line.type = 'phantom' # Need 1 imac_component for iMac self.bom_line_model.create({ diff --git a/stock_available_mrp/views/product_template_view.xml b/stock_available_mrp/views/product_template_view.xml index c58512d56..446e0c5c3 100644 --- a/stock_available_mrp/views/product_template_view.xml +++ b/stock_available_mrp/views/product_template_view.xml @@ -9,8 +9,16 @@ - - + + From 0787dcc2be3e74a4a7017592e57c0c0524c8e2b0 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sun, 12 Jun 2016 15:53:35 -0400 Subject: [PATCH 10/29] OCA Transbot updated translations from Transifex --- stock_available_mrp/__openerp__.py | 2 +- stock_available_mrp/i18n/hr_HR.po | 65 ++++++++++++++++++++++++++++++ stock_available_mrp/i18n/it.po | 64 +++++++++++++++++++++++++++++ stock_available_mrp/i18n/pt_BR.po | 65 ++++++++++++++++++++++++++++++ stock_available_mrp/i18n/zh_CN.po | 65 ++++++++++++++++++++++++++++++ 5 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 stock_available_mrp/i18n/hr_HR.po create mode 100644 stock_available_mrp/i18n/it.po create mode 100644 stock_available_mrp/i18n/pt_BR.po create mode 100644 stock_available_mrp/i18n/zh_CN.po diff --git a/stock_available_mrp/__openerp__.py b/stock_available_mrp/__openerp__.py index 0aa748b7e..da88f11bd 100644 --- a/stock_available_mrp/__openerp__.py +++ b/stock_available_mrp/__openerp__.py @@ -18,5 +18,5 @@ 'demo/mrp_bom.yml', ], 'license': 'AGPL-3', - 'installable': True, + 'installable': False, } diff --git a/stock_available_mrp/i18n/hr_HR.po b/stock_available_mrp/i18n/hr_HR.po new file mode 100644 index 000000000..59853430f --- /dev/null +++ b/stock_available_mrp/i18n/hr_HR.po @@ -0,0 +1,65 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_available_mrp +# +# Translators: +# Bole , 2016 +msgid "" +msgstr "" +"Project-Id-Version: stock-logistics-warehouse (9.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-06-12 19:59+0000\n" +"PO-Revision-Date: 2016-06-14 10:45+0000\n" +"Last-Translator: Bole \n" +"Language-Team: Croatian (Croatia) (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-9-0/language/hr_HR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: hr_HR\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#. module: stock_available_mrp +#: model:ir.ui.view,arch_db:stock_available_mrp.view_product_form_potential_qty +msgid "Potential" +msgstr "Potencijal" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_component_ids +msgid "Component ids" +msgstr "ID-ovi komponenata" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_potential_qty +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_template_potential_qty +msgid "Potential" +msgstr "Potencijal" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_product +msgid "Product" +msgstr "Proizvod" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_template +msgid "Product Template" +msgstr "Predložak proizvoda" + +#. module: stock_available_mrp +#: model:ir.model.fields,help:stock_available_mrp.field_product_product_potential_qty +msgid "" +"Quantity of this Product that could be produced using the materials already " +"at hand." +msgstr "Količina ovog proizvoda nije mogla biti proizvedena korištenjem trenutno raspoloživih materijala." + +#. module: stock_available_mrp +#: model:ir.model.fields,help:stock_available_mrp.field_product_template_potential_qty +msgid "" +"Quantity of this Product that could be produced using the materials already " +"at hand. If the product has several variants, this will be the biggest " +"quantity that can be made for a any single variant." +msgstr "Količina ovog proizvoda koja može biti proizvodedna raspoloživim količinama sirovina. Ako proizvod ima nekoliko varijanti, ovo će biti najveća moguća količina koja se može proizvesti za svaku pojedinu varijantu." + +#. module: stock_available_mrp +#: model:product.uom,name:stock_available_mrp.thousand +msgid "Thousand" +msgstr "Tisuću" diff --git a/stock_available_mrp/i18n/it.po b/stock_available_mrp/i18n/it.po new file mode 100644 index 000000000..bace38b97 --- /dev/null +++ b/stock_available_mrp/i18n/it.po @@ -0,0 +1,64 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_available_mrp +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: stock-logistics-warehouse (9.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-04 10:11+0000\n" +"PO-Revision-Date: 2016-04-27 11:10+0000\n" +"Last-Translator: <>\n" +"Language-Team: Italian (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-9-0/language/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: stock_available_mrp +#: model:ir.ui.view,arch_db:stock_available_mrp.view_product_form_potential_qty +msgid "Potential" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_component_ids +msgid "Component ids" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_potential_qty +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_template_potential_qty +msgid "Potential" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_product +msgid "Product" +msgstr "Prodotto" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_template +msgid "Product Template" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,help:stock_available_mrp.field_product_product_potential_qty +msgid "" +"Quantity of this Product that could be produced using the materials already " +"at hand." +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,help:stock_available_mrp.field_product_template_potential_qty +msgid "" +"Quantity of this Product that could be produced using the materials already " +"at hand. If the product has several variants, this will be the biggest " +"quantity that can be made for a any single variant." +msgstr "" + +#. module: stock_available_mrp +#: model:product.uom,name:stock_available_mrp.thousand +msgid "Thousand" +msgstr "" diff --git a/stock_available_mrp/i18n/pt_BR.po b/stock_available_mrp/i18n/pt_BR.po new file mode 100644 index 000000000..de9e46c7a --- /dev/null +++ b/stock_available_mrp/i18n/pt_BR.po @@ -0,0 +1,65 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_available_mrp +# +# Translators: +# Claudio Araujo Santos , 2016 +msgid "" +msgstr "" +"Project-Id-Version: stock-logistics-warehouse (9.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-07-03 20:56+0000\n" +"PO-Revision-Date: 2016-07-07 19:41+0000\n" +"Last-Translator: Claudio Araujo Santos \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-9-0/language/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: stock_available_mrp +#: model:ir.ui.view,arch_db:stock_available_mrp.view_product_form_potential_qty +msgid "Potential" +msgstr "Potencial" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_component_ids +msgid "Component ids" +msgstr "IDs de componentes" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_potential_qty +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_template_potential_qty +msgid "Potential" +msgstr "Potencial" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_product +msgid "Product" +msgstr "Produto" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_template +msgid "Product Template" +msgstr "Modelo Produto" + +#. module: stock_available_mrp +#: model:ir.model.fields,help:stock_available_mrp.field_product_product_potential_qty +msgid "" +"Quantity of this Product that could be produced using the materials already " +"at hand." +msgstr "Quantidade deste produto que poderia ser produzido usando os materiais já na mão." + +#. module: stock_available_mrp +#: model:ir.model.fields,help:stock_available_mrp.field_product_template_potential_qty +msgid "" +"Quantity of this Product that could be produced using the materials already " +"at hand. If the product has several variants, this will be the biggest " +"quantity that can be made for a any single variant." +msgstr "Quantidade deste produto que poderia ser produzido usando os materiais já na mão. Se o produto tiver várias variantes, esta será a maior quantidade que pode ser feito por qualquer uma única variante." + +#. module: stock_available_mrp +#: model:product.uom,name:stock_available_mrp.thousand +msgid "Thousand" +msgstr "Mil" diff --git a/stock_available_mrp/i18n/zh_CN.po b/stock_available_mrp/i18n/zh_CN.po new file mode 100644 index 000000000..f9a83998f --- /dev/null +++ b/stock_available_mrp/i18n/zh_CN.po @@ -0,0 +1,65 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_available_mrp +# +# Translators: +# Jeffery Chenn , 2016 +msgid "" +msgstr "" +"Project-Id-Version: stock-logistics-warehouse (9.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-28 10:00+0000\n" +"PO-Revision-Date: 2016-09-04 06:06+0000\n" +"Last-Translator: Jeffery Chenn \n" +"Language-Team: Chinese (China) (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-9-0/language/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: stock_available_mrp +#: model:ir.ui.view,arch_db:stock_available_mrp.view_product_form_potential_qty +msgid "Potential" +msgstr "潜在" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_component_ids +msgid "Component ids" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_potential_qty +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_template_potential_qty +msgid "Potential" +msgstr "潜在" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_product +msgid "Product" +msgstr "产品" + +#. module: stock_available_mrp +#: model:ir.model,name:stock_available_mrp.model_product_template +msgid "Product Template" +msgstr "产品模板" + +#. module: stock_available_mrp +#: model:ir.model.fields,help:stock_available_mrp.field_product_product_potential_qty +msgid "" +"Quantity of this Product that could be produced using the materials already " +"at hand." +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,help:stock_available_mrp.field_product_template_potential_qty +msgid "" +"Quantity of this Product that could be produced using the materials already " +"at hand. If the product has several variants, this will be the biggest " +"quantity that can be made for a any single variant." +msgstr "" + +#. module: stock_available_mrp +#: model:product.uom,name:stock_available_mrp.thousand +msgid "Thousand" +msgstr "千" From 42470f1f3b170e6c3116b108b49abd201f7011ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Pigeon?= Date: Tue, 9 May 2017 21:26:20 +0200 Subject: [PATCH 11/29] [10.0][MIG] port stock_available_mrp to 10.0 --- stock_available_mrp/README.rst | 1 + .../{__openerp__.py => __manifest__.py} | 6 +- stock_available_mrp/demo/mrp_bom.yml | 27 --- stock_available_mrp/demo/mrp_data.xml | 38 +++ stock_available_mrp/models/product_product.py | 217 +++++++++--------- .../models/product_template.py | 44 +--- .../tests/test_potential_qty.py | 184 ++++----------- .../views/product_template_view.xml | 6 +- 8 files changed, 209 insertions(+), 314 deletions(-) rename stock_available_mrp/{__openerp__.py => __manifest__.py} (85%) delete mode 100644 stock_available_mrp/demo/mrp_bom.yml create mode 100644 stock_available_mrp/demo/mrp_data.xml diff --git a/stock_available_mrp/README.rst b/stock_available_mrp/README.rst index b47d8dbf0..d0e8733f5 100644 --- a/stock_available_mrp/README.rst +++ b/stock_available_mrp/README.rst @@ -69,6 +69,7 @@ Contributors * Lionel Sausin (Numérigraphe) * many thanks to Graeme Gellatly for his advice and code review * Laurent Mignon +* Cédric Pigeon Maintainer ---------- diff --git a/stock_available_mrp/__openerp__.py b/stock_available_mrp/__manifest__.py similarity index 85% rename from stock_available_mrp/__openerp__.py rename to stock_available_mrp/__manifest__.py index da88f11bd..b70a3108b 100644 --- a/stock_available_mrp/__openerp__.py +++ b/stock_available_mrp/__manifest__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '9.0.1.0.0', + 'version': '10.0.1.0.0', "author": u"Numérigraphe," u"Odoo Community Association (OCA)", 'category': 'Hidden', @@ -15,8 +15,8 @@ 'views/product_template_view.xml', ], 'demo': [ - 'demo/mrp_bom.yml', + 'demo/mrp_data.xml', ], 'license': 'AGPL-3', - 'installable': False, + 'installable': True, } diff --git a/stock_available_mrp/demo/mrp_bom.yml b/stock_available_mrp/demo/mrp_bom.yml deleted file mode 100644 index bb197f44b..000000000 --- a/stock_available_mrp/demo/mrp_bom.yml +++ /dev/null @@ -1,27 +0,0 @@ -- Create a UoM in the category of PCE -- !record {model: product.uom, id: thousand}: - name: Thousand - factor: 0.001 - rounding: 0.001 - uom_type: bigger - category_id: product.product_uom_categ_unit - -- Add a BOM whereby 0.042K "RAM SR2" can be replaced with 13 dozens "HDD-SH1" + 8 CPUa8 with 50% efficiency. This lets us test UoM conversions for the finished product and the raw materials, as well as the unfolding of phantom BoMs -- !record {model: mrp.bom, id: sr2_from_hdd}: - product_id: product.product_product_14 - product_tmpl_id: product.product_product_14_product_template - product_uom: thousand - product_qty: 0.042 - type: phantom - sequence: -1 - product_efficiency: 0.5 -- !record {model: mrp.bom.line, id: sr2_from_hdd_line1}: - bom_id: sr2_from_hdd - product_id: product.product_product_18 - product_qty: 13 - product_uom: product.product_uom_dozen -- !record {model: mrp.bom.line, id: sr2_from_hdd_line2}: - bom_id: sr2_from_hdd - product_id: product.product_product_23 - product_qty: 8 - product_uom: product.product_uom_unit diff --git a/stock_available_mrp/demo/mrp_data.xml b/stock_available_mrp/demo/mrp_data.xml new file mode 100644 index 000000000..0099e9349 --- /dev/null +++ b/stock_available_mrp/demo/mrp_data.xml @@ -0,0 +1,38 @@ + + + + PCSC234-WHITE + + + + + + + + + + + + + + Apple Wireless Keyboard + + 10.0 + 47.0 + consu + + + E-COM10-WHITE + + + + + 1 + + 5 + + + + diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 790ca16a4..9417b3052 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -2,133 +2,140 @@ # © 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from collections import Counter - -from openerp import models, fields, api -from openerp.addons import decimal_precision as dp - -from openerp.exceptions import AccessError +from collections import Counter, defaultdict +from odoo import api, fields, models class ProductProduct(models.Model): + _inherit = 'product.product' - potential_qty = fields.Float( - compute='_get_potential_qty', - 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.") - - # Needed for fields dependencies - # When self.potential_qty is compute, we want to force the ORM - # to compute all the components potential_qty too. - component_ids = fields.Many2many( - comodel_name='product.product', - compute='_get_component_ids', + bom_id = fields.Many2one( + 'mrp.bom', + compute='_compute_bom_id', + string='Bill of Materials' ) - @api.multi - @api.depends('potential_qty') - def _immediately_usable_qty(self): - """Add the potential quantity to the quantity available to promise. - - This is the same implementation as for templates.""" - super(ProductProduct, self)._immediately_usable_qty() - for product in self: - product.immediately_usable_qty += product.potential_qty + @api.depends('virtual_available') + def _compute_available_quantities(self): + super(ProductProduct, self)._compute_available_quantities() @api.multi - @api.depends('component_ids.potential_qty') - def _get_potential_qty(self): - """Compute the potential qty based on the available components.""" - bom_obj = self.env['mrp.bom'] - uom_obj = self.env['product.uom'] - + def _compute_bom_id(self): + domain = [('product_id', 'in', self.ids)] + product_tmpl_ids = [] + bom_product_ids = self.env['mrp.bom'].search(domain) + # find bom linked to a product + bom_by_product_id = { + b.product_id.id: b for b in bom_product_ids} + product_id_found = bom_by_product_id.keys() for product in self: - bom_id = bom_obj._bom_find(product_id=product.id) - if not bom_id: - product.potential_qty = 0.0 - continue + if product.id not in product_id_found: + product_tmpl_ids.append(product.product_tmpl_id.id) + domain = [('product_id', '=', False), + ('product_tmpl_id', 'in', product_tmpl_ids)] + # find boms linked to the product template + bom_product_template = self.env['mrp.bom'].search(domain) + bom_by_product_tmpl_id = { + b.product_tmpl_id.id: b for b in bom_product_template} + for product in self: + product.bom_id = bom_by_product_id.get( + product.id, + bom_by_product_tmpl_id.get(product.product_tmpl_id.id) + ) - bom = bom_obj.browse(bom_id) + @api.multi + def _compute_available_quantities_dict(self): + res = super(ProductProduct, self)._compute_available_quantities_dict() - # Need by product (same product can be in many BOM lines/levels) - try: - component_needs = self._get_components_needs(product, bom) - except AccessError: - # If user doesn't have access to BOM - # he can't see potential_qty - component_needs = None + # compute qty for product with bom + product_with_bom = self.filtered(lambda p: p.bom_id) - if not component_needs: - # The BoM has no line we can use - product.potential_qty = 0.0 - - else: - # Find the lowest quantity we can make with the stock at hand - components_potential_qty = min( - [self._get_component_qty(component) // need - for component, need in component_needs.items()] - ) - - # Compute with bom quantity - bom_qty = uom_obj._compute_qty_obj( - bom.product_uom, - bom.product_qty, - bom.product_tmpl_id.uom_id - ) - product.potential_qty = bom_qty * components_potential_qty - - def _get_component_qty(self, component): - """ Return the component qty to use based en company settings. - - :type component: product_product - :rtype: float - """ + if not product_with_bom: + return res icp = self.env['ir.config_parameter'] stock_available_mrp_based_on = icp.get_param( 'stock_available_mrp_based_on', 'qty_available' ) - return component[stock_available_mrp_based_on] + # from here we start the computation of bom qties in an isolated + # environment to avoid trouble with prefetch and cache + product_with_bom = product_with_bom.with_context( + unique=models.NewId()).with_prefetch(defaultdict(set)) - def _get_components_needs(self, product, bom): - """ Return the needed qty of each compoments in the *bom* of *product*. + # explode all boms at once + exploded_boms = product_with_bom._explode_boms() - :type product: product_product - :type bom: mrp_bom + # extract the list of product used as bom component + product_components_ids = set() + for exploded_components in exploded_boms.values(): + for bom_component in exploded_components: + product_components_ids.add(bom_component[0].product_id.id) + + # Compute stock for product components. + # {'productid': {field_name: qty}} + component_products = product_with_bom.browse( + product_components_ids) + if stock_available_mrp_based_on in res.keys(): + # If the qty is computed by the same method use it to avoid + # stressing the cache + component_qties = \ + component_products._compute_available_quantities_dict() + else: + # The qty is a field computed by an other method than the + # current one. Take the value on the record. + component_qties = { + p.id: { + stock_available_mrp_based_on: p[ + stock_available_mrp_based_on]} for p in + component_products} + + for product in product_with_bom: + # Need by product (same product can be in many BOM lines/levels) + exploded_components = exploded_boms[product.id] + component_needs = product._get_components_needs( + exploded_components) + if not component_needs: + # The BoM has no line we can use + potential_qty = 0.0 + + else: + # Find the lowest quantity we can make with the stock at hand + components_potential_qty = min( + [component_qties[component.id][ + stock_available_mrp_based_on] // need + for component, need in component_needs.items()] + ) + + potential_qty = (product.bom_id.product_qty * + components_potential_qty) + + res[product.id]['potential_qty'] = potential_qty + res[product.id]['immediately_usable_qty'] += potential_qty + + return res + + @api.multi + def _explode_boms(self): + """ + return a dict by product_id of exploded bom lines + :return: + """ + exploded_boms = {} + for rec in self: + exploded_boms[rec.id] = rec.bom_id.explode(rec, 1.0)[1] + return exploded_boms + + @api.model + def _get_components_needs(self, exploded_components): + """ Return the needed qty of each compoments in the exploded_components + + :type exploded_components :rtype: collections.Counter """ - bom_obj = self.env['mrp.bom'] - uom_obj = self.env['product.uom'] - product_obj = self.env['product.product'] - needs = Counter() - for bom_component in bom_obj._bom_explode(bom, product, 1.0)[0]: - product_uom = uom_obj.browse(bom_component['product_uom']) - component = product_obj.browse(bom_component['product_id']) - - component_qty = uom_obj._compute_qty_obj( - product_uom, - bom_component['product_qty'], - component.uom_id, - ) - needs += Counter( - {component: component_qty} - ) + for bom_component in exploded_components: + component = bom_component[0].product_id + needs += Counter({component: bom_component[1]['qty']}) return needs - - def _get_component_ids(self): - """ Compute component_ids by getting all the components for - this product. - """ - bom_obj = self.env['mrp.bom'] - - bom_id = bom_obj._bom_find(product_id=self.id) - if bom_id: - bom = bom_obj.browse(bom_id) - for bom_component in bom_obj._bom_explode(bom, self, 1.0)[0]: - self.component_ids |= self.browse(bom_component['product_id']) diff --git a/stock_available_mrp/models/product_template.py b/stock_available_mrp/models/product_template.py index f87ef6901..b5eb79cf7 100644 --- a/stock_available_mrp/models/product_template.py +++ b/stock_available_mrp/models/product_template.py @@ -2,45 +2,17 @@ # © 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import models, fields, api -from openerp.addons import decimal_precision as dp +from odoo import models, api class ProductTemplate(models.Model): _inherit = 'product.template' - potential_qty = fields.Float( - compute='_get_potential_qty', - 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. " - "If the product has several variants, this will be the biggest " - "quantity that can be made for a any single variant.") - @api.multi - @api.depends('potential_qty') - def _immediately_usable_qty(self): - """Add the potential quantity to the quantity available to promise. - - This is the same implementation as for variants.""" - super(ProductTemplate, self)._immediately_usable_qty() - for tmpl in self: - tmpl.immediately_usable_qty += tmpl.potential_qty - - @api.multi - @api.depends('product_variant_ids.potential_qty') - def _get_potential_qty(self): - """Compute the potential as the max of all the variants's potential. - - We can't add the potential of variants: if they share components we - may not be able to make all the variants. - So we set the arbitrary rule that we can promise up to the biggest - variant's potential. - """ - for tmpl in self: - if not tmpl.product_variant_ids: - continue - tmpl.potential_qty = max( - [v.potential_qty for v in tmpl.product_variant_ids]) + def _compute_available_quantities_dict(self): + res = super(ProductTemplate, self)._compute_available_quantities_dict() + for template in self: + if template.bom_ids: + res[template.id]['immediately_usable_qty'] =\ + res[template.id]['potential_qty'] + return res diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index 896655dae..83cfba1ef 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -2,8 +2,8 @@ # © 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp.tests.common import TransactionCase -from openerp.osv.expression import TRUE_LEAF +from odoo.tests.common import TransactionCase +from odoo.osv.expression import TRUE_LEAF class TestPotentialQty(TransactionCase): @@ -23,21 +23,19 @@ class TestPotentialQty(TransactionCase): def setup_demo_data(self): #  An interesting product (multi-line BoM, variants) self.tmpl = self.browse_ref( - 'product.product_product_4_product_template') + 'mrp.product_product_build_kit_product_template') #  First variant - self.var1 = self.browse_ref('product.product_product_4c') + self.var1 = self.browse_ref('mrp.product_product_build_kit') #  Second variant - self.var2 = self.browse_ref('product.product_product_4') + self.var2 = self.browse_ref( + 'stock_available_mrp.product_kit_1a') # Components that can be used to make the product component_ids = [ - # CPUa8 - self.ref('product.product_product_23'), - # RAM-SR2 - self.ref('product.product_product_14'), - # HDD SH-2 replaces RAM-SR2 through our demo phantom BoM - self.ref('product.product_product_18'), - # RAM-SR3 - self.ref('product.product_product_15')] + # KeyBoard + self.ref('product.product_product_9'), + # Mouse + self.ref('product.product_product_12'), + ] # Zero-out the inventory of all variants and components for component_id in ( @@ -53,7 +51,7 @@ class TestPotentialQty(TransactionCase): inventory.action_done() #  A product without a BoM - self.product_wo_bom = self.browse_ref('product.product_product_23') + self.product_wo_bom = self.browse_ref('product.product_product_11') # Record the initial quantity available for sale self.initial_usable_qties = {i.id: i.immediately_usable_qty @@ -92,14 +90,12 @@ class TestPotentialQty(TransactionCase): 'product_tmpl_id': product.product_tmpl_id.id, 'product_id': product.id, 'product_qty': product_qty, - 'product_uom': self.ref('product.product_uom_unit'), 'routing_id': routing_id, }) self.bom_line_model.create({ 'bom_id': bom.id, 'product_id': sub_product.id, 'product_qty': sub_product_qty, - 'product_uom': self.ref('product.product_uom_unit'), }) return bom @@ -122,7 +118,7 @@ class TestPotentialQty(TransactionCase): def test_potential_qty_no_bom_for_company(self): chicago_id = self.ref('stock.res_company_1') - # Receive 1000x CPUa8s owned by Chicago + # Receive 1000x CPUI5s owned by Chicago inventory = self.env['stock.inventory'].create( {'name': 'Receive CPUa8', 'company_id': chicago_id, @@ -132,12 +128,12 @@ class TestPotentialQty(TransactionCase): self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'company_id': chicago_id, - 'product_id': self.ref('product.product_product_23'), + 'product_id': self.ref('product.product_product_9'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) inventory.action_done() - # Put RAM-SR3 owned by Chicago for 1000x the 1st variant in main WH + # Put RAM-SR5 owned by Chicago for 1000x the 1st variant in main WH inventory = self.env['stock.inventory'].create( {'name': 'components for 1st variant', 'company_id': chicago_id, @@ -147,7 +143,7 @@ class TestPotentialQty(TransactionCase): self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'company_id': chicago_id, - 'product_id': self.ref('product.product_product_15'), + 'product_id': self.ref('product.product_product_12'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) inventory.action_done() @@ -187,44 +183,21 @@ class TestPotentialQty(TransactionCase): self.assertPotentialQty( test_user_tmpl, 1000.0, '') - def test_group_mrp_missing(self): - test_user = self.env['res.users'].create({ - 'name': 'test_demo', - 'login': 'test_demo', - 'company_id': self.ref('base.main_company'), - 'company_ids': [(4, self.ref('base.main_company'))], - 'groups_id': [(4, self.ref('stock.group_stock_user'))], - }) - - p1 = self.product_model.create({'name': 'Test P1'}) - p2 = self.product_model.create({'name': 'Test P2'}) - - self.create_simple_bom(p1, p2, - routing_id=self.ref('mrp.mrp_routing_0')) - self.create_inventory(p2.id, 1) - - test_user_p1 = p1.sudo(test_user) - # Test user doesn't have access to mrp_routing, can't compute potential - self.assertEqual(0, test_user_p1.potential_qty) - - test_user.groups_id = [(4, self.ref('mrp.group_mrp_user'))] - self.assertEqual(1, test_user_p1.potential_qty) - def test_potential_qty(self): for i in [self.tmpl, self.var1, self.var2]: self.assertPotentialQty( i, 0.0, "The potential quantity should start at 0") - # Receive 1000x CPUa8s + # Receive 1000x Mouses inventory = self.env['stock.inventory'].create( - {'name': 'Receive CPUa8', + {'name': 'Receive Mouses', 'location_id': self.wh_main.lot_stock_id.id, 'filter': 'partial'}) inventory.prepare_inventory() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_23'), + 'product_id': self.ref('product.product_product_12'), 'location_id': self.wh_main.lot_stock_id.id, 'product_qty': 1000.0}) inventory.action_done() @@ -234,7 +207,7 @@ class TestPotentialQty(TransactionCase): "Receiving a single component should not change the " "potential of %s" % i) - # Receive enough RAM-SR3 to make 1000x the 1st variant in main WH + # Receive enough keyboard to make 1000x the 1st variant in main WH inventory = self.env['stock.inventory'].create( {'name': 'components for 1st variant', 'location_id': self.wh_main.lot_stock_id.id, @@ -242,7 +215,8 @@ class TestPotentialQty(TransactionCase): inventory.prepare_inventory() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_15'), + 'product_id': self.ref( + 'product.product_product_9'), 'location_id': self.wh_main.lot_stock_id.id, 'product_qty': 1000.0}) inventory.action_done() @@ -257,9 +231,7 @@ class TestPotentialQty(TransactionCase): "Receiving variant 1's component should not change " "variant 2's potential") - # Receive enough components to make 42X the 2nd variant at Chicago - # need 13 dozens of HDD with 50% efficiency to build 42 RAM - # So 313 HDD (with rounding) for 42 RAM + # Receive enough components to make 313 the 2nd variant at Chicago inventory = self.env['stock.inventory'].create( {'name': 'components for 2nd variant', 'location_id': self.wh_ch.lot_stock_id.id, @@ -267,12 +239,13 @@ class TestPotentialQty(TransactionCase): inventory.prepare_inventory() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_23'), + 'product_id': self.ref('product.product_product_12'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_18'), + 'product_id': self.ref( + 'stock_available_mrp.product_product_9_white'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 313.0}) inventory.action_done() @@ -284,14 +257,14 @@ class TestPotentialQty(TransactionCase): "Receiving variant 2's component should not change " "variant 1's potential") self.assertPotentialQty( - self.var2, 42.0, + self.var2, 313.0, "Wrong variant 2 potential after receiving components") # Check by warehouse self.assertPotentialQty( self.tmpl.with_context(warehouse=self.wh_main.id), 1000.0, "Wrong potential quantity in main WH") self.assertPotentialQty( - self.tmpl.with_context(warehouse=self.wh_ch.id), 42.0, + self.tmpl.with_context(warehouse=self.wh_ch.id), 313.0, "Wrong potential quantity in Chicago WH") # Check by location self.assertPotentialQty( @@ -301,7 +274,7 @@ class TestPotentialQty(TransactionCase): self.assertPotentialQty( self.tmpl.with_context( location=self.wh_ch.lot_stock_id.id), - 42.0, + 313.0, "Wrong potential quantity in Chicago WH location") def test_multi_unit_recursive_bom(self): @@ -324,12 +297,10 @@ class TestPotentialQty(TransactionCase): 'product_id': p1.id, }) - # 1 dozen of component self.bom_line_model.create({ 'bom_id': bom_p1.id, 'product_id': p3.id, 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_dozen'), }) # Two p2 which have a bom @@ -337,7 +308,6 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p1.id, 'product_id': p2.id, 'product_qty': 2, - 'product_uom': self.ref('product.product_uom_unit'), }) bom_p2 = self.bom_model.create({ @@ -351,12 +321,11 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p2.id, 'product_id': p3.id, 'product_qty': 2, - 'product_uom': self.ref('product.product_uom_unit'), }) p1.refresh() - # Need a least 1 dozen + 2 * 2 = 16 units for one P1 + # Need a least 5 units for one P1 self.assertEqual(0, p1.potential_qty) self.create_inventory(p3.id, 1) @@ -364,89 +333,30 @@ class TestPotentialQty(TransactionCase): p1.refresh() self.assertEqual(0, p1.potential_qty) - self.create_inventory(p3.id, 15) + self.create_inventory(p3.id, 3) p1.refresh() self.assertEqual(0, p1.potential_qty) - self.create_inventory(p3.id, 16) + self.create_inventory(p3.id, 5) p1.refresh() self.assertEqual(1.0, p1.potential_qty) - self.create_inventory(p3.id, 25) + self.create_inventory(p3.id, 6) p1.refresh() self.assertEqual(1.0, p1.potential_qty) - self.create_inventory(p3.id, 32) + self.create_inventory(p3.id, 10) p1.refresh() self.assertEqual(2.0, p1.potential_qty) - def test_bom_qty_and_efficiency(self): - - p1 = self.product_model.create({ - 'name': 'Test product with BOM', - }) - - p2 = self.product_model.create({ - 'name': 'Test sub product with BOM', - }) - - p3 = self.product_model.create({ - 'name': 'Test component' - }) - - # A bom produce 2 dozen of P1 - bom_p1 = self.bom_model.create({ - 'product_tmpl_id': p1.product_tmpl_id.id, - 'product_id': p1.id, - 'product_qty': 2, - 'product_uom': self.ref('product.product_uom_dozen'), - }) - - # Need 5 p2 for that - self.bom_line_model.create({ - 'bom_id': bom_p1.id, - 'product_id': p2.id, - 'product_qty': 5, - 'product_uom': self.ref('product.product_uom_unit'), - 'product_efficiency': 0.8, - }) - - # Which need 1 dozen of P3 - bom_p2 = self.bom_model.create({ - 'product_tmpl_id': p2.product_tmpl_id.id, - 'product_id': p2.id, - 'type': 'phantom', - }) - self.bom_line_model.create({ - 'bom_id': bom_p2.id, - 'product_id': p3.id, - 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_dozen'), - }) - - p1.refresh() - self.assertEqual(0, p1.potential_qty) - - self.create_inventory(p3.id, 60) - - p1.refresh() - self.assertEqual(0, p1.potential_qty) - - # Need 5 * 1 dozen => 60 - # But 80% lost each dozen, need 3 more by dozen => 60 + 5 *3 => 75 - self.create_inventory(p3.id, 75) - - p1.refresh() - self.assertEqual(24, p1.potential_qty) - def test_component_stock_choice(self): # Test to change component stock for compute BOM stock # Get a demo product with outgoing move (qty: 3) - imac = self.browse_ref('product.product_product_8') + prod = self.browse_ref('product.product_product_20') # Set on hand qty - self.create_inventory(imac.id, 3) + self.create_inventory(prod.id, 3) # Create a product with BOM p1 = self.product_model.create({ @@ -456,15 +366,13 @@ class TestPotentialQty(TransactionCase): 'product_tmpl_id': p1.product_tmpl_id.id, 'product_id': p1.id, 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_unit'), }) - # Need 1 iMac for that + # Need 1 prod for that self.bom_line_model.create({ 'bom_id': bom_p1.id, - 'product_id': imac.id, + 'product_id': prod.id, 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_unit'), }) # Default component is qty_available @@ -479,25 +387,23 @@ class TestPotentialQty(TransactionCase): self.assertEqual(0.0, p1.potential_qty) # If iMac has a Bom and can be manufactured - imac_component = self.product_model.create({ - 'name': 'iMac component', + component = self.product_model.create({ + 'name': 'component', }) - self.create_inventory(imac_component.id, 5) + self.create_inventory(component.id, 5) imac_bom = self.bom_model.create({ - 'product_tmpl_id': imac.product_tmpl_id.id, - 'product_id': imac.id, + 'product_tmpl_id': prod.product_tmpl_id.id, + 'product_id': prod.id, 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_unit'), 'type': 'phantom', }) - # Need 1 imac_component for iMac + # Need 1 component for prod self.bom_line_model.create({ 'bom_id': imac_bom.id, - 'product_id': imac_component.id, + 'product_id': component.id, 'product_qty': 1, - 'product_uom': self.ref('product.product_uom_unit'), }) p1.refresh() diff --git a/stock_available_mrp/views/product_template_view.xml b/stock_available_mrp/views/product_template_view.xml index 446e0c5c3..fa20ec54b 100644 --- a/stock_available_mrp/views/product_template_view.xml +++ b/stock_available_mrp/views/product_template_view.xml @@ -1,6 +1,5 @@ - - + Potential quantity on product form @@ -23,5 +22,4 @@ - - + From 922d42d62fe47267cecd0c5b42725cedaf83d07a Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Wed, 22 Aug 2018 17:59:25 +0200 Subject: [PATCH 12/29] [IMP] stock_available_mrp: readability + change compute bom_id + use recordset + Condition never statisfied --- stock_available_mrp/models/product_product.py | 66 +++++++++---------- .../models/product_template.py | 7 +- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 9417b3052..37e92d328 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -2,8 +2,9 @@ # © 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from collections import Counter, defaultdict +from collections import Counter from odoo import api, fields, models +from odoo.fields import first class ProductProduct(models.Model): @@ -16,40 +17,46 @@ class ProductProduct(models.Model): string='Bill of Materials' ) - @api.depends('virtual_available') + @api.depends('virtual_available', 'bom_id', 'bom_id.product_qty') def _compute_available_quantities(self): super(ProductProduct, self)._compute_available_quantities() @api.multi + def _get_bom_id_domain(self): + """ + Real multi domain + :return: + """ + return [ + '|', + ('product_id', 'in', self.ids), + '&', + ('product_id', '=', False), + ('product_tmpl_id', 'in', self.mapped('product_tmpl_id.id')) + ] + + @api.multi + @api.depends('product_tmpl_id') def _compute_bom_id(self): - domain = [('product_id', 'in', self.ids)] - product_tmpl_ids = [] - bom_product_ids = self.env['mrp.bom'].search(domain) - # find bom linked to a product - bom_by_product_id = { - b.product_id.id: b for b in bom_product_ids} - product_id_found = bom_by_product_id.keys() + bom_obj = self.env['mrp.bom'] + boms = bom_obj.search( + self._get_bom_id_domain(), + order='sequence, product_id', + ) for product in self: - if product.id not in product_id_found: - product_tmpl_ids.append(product.product_tmpl_id.id) - domain = [('product_id', '=', False), - ('product_tmpl_id', 'in', product_tmpl_ids)] - # find boms linked to the product template - bom_product_template = self.env['mrp.bom'].search(domain) - bom_by_product_tmpl_id = { - b.product_tmpl_id.id: b for b in bom_product_template} - for product in self: - product.bom_id = bom_by_product_id.get( - product.id, - bom_by_product_tmpl_id.get(product.product_tmpl_id.id) + product_boms = boms.filtered( + lambda b: b.product_id == product or + (not b.product_id and + b.product_tmpl_id == product.product_tmpl_id) ) + if product_boms: + product.bom_id = first(product_boms) @api.multi def _compute_available_quantities_dict(self): res = super(ProductProduct, self)._compute_available_quantities_dict() - # compute qty for product with bom - product_with_bom = self.filtered(lambda p: p.bom_id) + product_with_bom = self.filtered('bom_id') if not product_with_bom: return res @@ -58,25 +65,18 @@ class ProductProduct(models.Model): 'stock_available_mrp_based_on', 'qty_available' ) - # from here we start the computation of bom qties in an isolated - # environment to avoid trouble with prefetch and cache - product_with_bom = product_with_bom.with_context( - unique=models.NewId()).with_prefetch(defaultdict(set)) - # explode all boms at once exploded_boms = product_with_bom._explode_boms() # extract the list of product used as bom component - product_components_ids = set() + component_products = self.env['product.product'].browse() for exploded_components in exploded_boms.values(): for bom_component in exploded_components: - product_components_ids.add(bom_component[0].product_id.id) + component_products |= first(bom_component).product_id # Compute stock for product components. # {'productid': {field_name: qty}} - component_products = product_with_bom.browse( - product_components_ids) - if stock_available_mrp_based_on in res.keys(): + if res and stock_available_mrp_based_on in res.values()[0]: # If the qty is computed by the same method use it to avoid # stressing the cache component_qties = \ diff --git a/stock_available_mrp/models/product_template.py b/stock_available_mrp/models/product_template.py index b5eb79cf7..7e92f7b66 100644 --- a/stock_available_mrp/models/product_template.py +++ b/stock_available_mrp/models/product_template.py @@ -11,8 +11,7 @@ class ProductTemplate(models.Model): @api.multi def _compute_available_quantities_dict(self): res = super(ProductTemplate, self)._compute_available_quantities_dict() - for template in self: - if template.bom_ids: - res[template.id]['immediately_usable_qty'] =\ - res[template.id]['potential_qty'] + for template in self.filtered('bom_ids'): + res[template.id]['immediately_usable_qty'] =\ + res[template.id]['potential_qty'] return res From 7351ff7e7604fd5a54e3e52bc908c4242a924e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Pigeon?= Date: Mon, 3 Sep 2018 16:20:28 +0200 Subject: [PATCH 13/29] [ADD] include new improvments --- stock_available_mrp/models/product_product.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 37e92d328..57a3b1d4d 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -54,12 +54,13 @@ class ProductProduct(models.Model): @api.multi def _compute_available_quantities_dict(self): - res = super(ProductProduct, self)._compute_available_quantities_dict() + res, stock_dict = super(ProductProduct, + self)._compute_available_quantities_dict() # compute qty for product with bom product_with_bom = self.filtered('bom_id') if not product_with_bom: - return res + return res, stock_dict icp = self.env['ir.config_parameter'] stock_available_mrp_based_on = icp.get_param( 'stock_available_mrp_based_on', 'qty_available' @@ -79,7 +80,7 @@ class ProductProduct(models.Model): if res and stock_available_mrp_based_on in res.values()[0]: # If the qty is computed by the same method use it to avoid # stressing the cache - component_qties = \ + component_qties, _ = \ component_products._compute_available_quantities_dict() else: # The qty is a field computed by an other method than the @@ -113,7 +114,7 @@ class ProductProduct(models.Model): res[product.id]['potential_qty'] = potential_qty res[product.id]['immediately_usable_qty'] += potential_qty - return res + return res, stock_dict @api.multi def _explode_boms(self): From 1daa97a8e5b619d371b0a3101c42a7671b6e4b2f Mon Sep 17 00:00:00 2001 From: Timon Tschanz Date: Mon, 1 Oct 2018 17:24:34 +0200 Subject: [PATCH 14/29] [FIX] stock_available_mrp: remove duplicate button + fix calculation of immediately usable qty --- stock_available_mrp/__manifest__.py | 3 --- .../models/product_template.py | 2 +- .../views/product_template_view.xml | 25 ------------------- 3 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 stock_available_mrp/views/product_template_view.xml diff --git a/stock_available_mrp/__manifest__.py b/stock_available_mrp/__manifest__.py index b70a3108b..5564377f3 100644 --- a/stock_available_mrp/__manifest__.py +++ b/stock_available_mrp/__manifest__.py @@ -11,9 +11,6 @@ 'stock_available', 'mrp' ], - 'data': [ - 'views/product_template_view.xml', - ], 'demo': [ 'demo/mrp_data.xml', ], diff --git a/stock_available_mrp/models/product_template.py b/stock_available_mrp/models/product_template.py index 7e92f7b66..5ae46c3a5 100644 --- a/stock_available_mrp/models/product_template.py +++ b/stock_available_mrp/models/product_template.py @@ -13,5 +13,5 @@ class ProductTemplate(models.Model): res = super(ProductTemplate, self)._compute_available_quantities_dict() for template in self.filtered('bom_ids'): res[template.id]['immediately_usable_qty'] =\ - res[template.id]['potential_qty'] + template.virtual_available + res[template.id]['potential_qty'] return res diff --git a/stock_available_mrp/views/product_template_view.xml b/stock_available_mrp/views/product_template_view.xml deleted file mode 100644 index fa20ec54b..000000000 --- a/stock_available_mrp/views/product_template_view.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - Potential quantity on product form - product.template - form - - - - - - - - - - From 6a444a5bcc2ad03af6fd27438c5b182d1791c671 Mon Sep 17 00:00:00 2001 From: Timon Tschanz Date: Fri, 4 Jan 2019 13:03:03 +0100 Subject: [PATCH 15/29] [11.0][MIG] port stock_available_mrp to 11.0 --- stock_available_mrp/__init__.py | 3 +- stock_available_mrp/__manifest__.py | 10 +-- stock_available_mrp/demo/mrp_data.xml | 2 +- stock_available_mrp/i18n/de.po | 34 ++++++-- stock_available_mrp/i18n/es.po | 29 +++++-- stock_available_mrp/i18n/fi.po | 29 +++++-- stock_available_mrp/i18n/fr.po | 29 +++++-- stock_available_mrp/i18n/hr_HR.po | 80 +++++++++++------- stock_available_mrp/i18n/it.po | 42 ++++----- stock_available_mrp/i18n/pt_BR.po | 76 ++++++++++------- stock_available_mrp/i18n/sl.po | 37 ++++++-- .../i18n/stock_available_mrp.pot | 35 +++++--- stock_available_mrp/i18n/zh_CN.po | 51 ++++++----- stock_available_mrp/models/__init__.py | 3 +- stock_available_mrp/models/product_product.py | 21 +++-- .../models/product_template.py | 3 +- .../static/description/icon.png | Bin 0 -> 9455 bytes stock_available_mrp/tests/__init__.py | 3 +- .../tests/test_potential_qty.py | 65 ++++++++------ 19 files changed, 352 insertions(+), 200 deletions(-) create mode 100644 stock_available_mrp/static/description/icon.png diff --git a/stock_available_mrp/__init__.py b/stock_available_mrp/__init__.py index 0443836b4..2f72d6f91 100644 --- a/stock_available_mrp/__init__.py +++ b/stock_available_mrp/__init__.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -# © 2014 Numérigraphe SARL +# Copyright 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import models diff --git a/stock_available_mrp/__manifest__.py b/stock_available_mrp/__manifest__.py index 5564377f3..58a507f11 100644 --- a/stock_available_mrp/__manifest__.py +++ b/stock_available_mrp/__manifest__.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- -# © 2014 Numérigraphe SARL, Camptocamp +# Copyright 2014 Numérigraphe SARL, Camptocamp # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '10.0.1.0.0', - "author": u"Numérigraphe," - u"Odoo Community Association (OCA)", + 'version': '11.0.1.0.0', + "author": "Numérigraphe," + "Odoo Community Association (OCA)", + 'website': 'https://github.com/OCA/stock-logistics-warehouse', 'category': 'Hidden', 'depends': [ 'stock_available', diff --git a/stock_available_mrp/demo/mrp_data.xml b/stock_available_mrp/demo/mrp_data.xml index 0099e9349..a41a04f4c 100644 --- a/stock_available_mrp/demo/mrp_data.xml +++ b/stock_available_mrp/demo/mrp_data.xml @@ -21,7 +21,7 @@ 10.0 47.0 - consu + product E-COM10-WHITE diff --git a/stock_available_mrp/i18n/de.po b/stock_available_mrp/i18n/de.po index 3fc625b7f..deb92b8bd 100644 --- a/stock_available_mrp/i18n/de.po +++ b/stock_available_mrp/i18n/de.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_available_mrp -# +# # Translators: # Rudolf Schnapka , 2016 msgid "" @@ -11,13 +11,25 @@ msgstr "" "POT-Creation-Date: 2016-01-14 01:38+0000\n" "PO-Revision-Date: 2016-01-14 09:35+0000\n" "Last-Translator: Rudolf Schnapka \n" -"Language-Team: German (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/de/)\n" +"Language-Team: German (http://www.transifex.com/oca/OCA-stock-logistics-" +"warehouse-8-0/language/de/)\n" +"Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#. module: stock_available_mrp +#: model:product.product,name:stock_available_mrp.product_product_9_white +#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template +msgid "Apple Wireless Keyboard" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id +msgid "Bill of Materials" +msgstr "" + #. module: stock_available_mrp #: model:ir.model,name:stock_available_mrp.model_product_product msgid "Product" @@ -29,6 +41,16 @@ msgid "Product Template" msgstr "Produktvorlage" #. module: stock_available_mrp -#: model:product.uom,name:stock_available_mrp.thousand -msgid "Thousand" -msgstr "Tausend" +#: model:product.product,name:stock_available_mrp.product_kit_1a +#: model:product.template,name:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build Kit" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,description:stock_available_mrp.product_kit_1a +#: model:product.template,description:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build kit." +msgstr "" + +#~ msgid "Thousand" +#~ msgstr "Tausend" diff --git a/stock_available_mrp/i18n/es.po b/stock_available_mrp/i18n/es.po index aab2eb182..1eb112127 100644 --- a/stock_available_mrp/i18n/es.po +++ b/stock_available_mrp/i18n/es.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_available_mrp -# +# # Translators: msgid "" msgstr "" @@ -10,13 +10,25 @@ msgstr "" "POT-Creation-Date: 2016-01-14 01:38+0000\n" "PO-Revision-Date: 2016-01-13 16:35+0000\n" "Last-Translator: <>\n" -"Language-Team: Spanish (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/es/)\n" +"Language-Team: Spanish (http://www.transifex.com/oca/OCA-stock-logistics-" +"warehouse-8-0/language/es/)\n" +"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#. module: stock_available_mrp +#: model:product.product,name:stock_available_mrp.product_product_9_white +#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template +msgid "Apple Wireless Keyboard" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id +msgid "Bill of Materials" +msgstr "" + #. module: stock_available_mrp #: model:ir.model,name:stock_available_mrp.model_product_product msgid "Product" @@ -28,6 +40,13 @@ msgid "Product Template" msgstr "Plantilla de producto" #. module: stock_available_mrp -#: model:product.uom,name:stock_available_mrp.thousand -msgid "Thousand" +#: model:product.product,name:stock_available_mrp.product_kit_1a +#: model:product.template,name:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build Kit" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,description:stock_available_mrp.product_kit_1a +#: model:product.template,description:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build kit." msgstr "" diff --git a/stock_available_mrp/i18n/fi.po b/stock_available_mrp/i18n/fi.po index fa81ddfe0..8c6389aee 100644 --- a/stock_available_mrp/i18n/fi.po +++ b/stock_available_mrp/i18n/fi.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_available_mrp -# +# # Translators: msgid "" msgstr "" @@ -10,13 +10,25 @@ msgstr "" "POT-Creation-Date: 2016-01-14 01:38+0000\n" "PO-Revision-Date: 2016-01-13 16:35+0000\n" "Last-Translator: <>\n" -"Language-Team: Finnish (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/fi/)\n" +"Language-Team: Finnish (http://www.transifex.com/oca/OCA-stock-logistics-" +"warehouse-8-0/language/fi/)\n" +"Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#. module: stock_available_mrp +#: model:product.product,name:stock_available_mrp.product_product_9_white +#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template +msgid "Apple Wireless Keyboard" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id +msgid "Bill of Materials" +msgstr "" + #. module: stock_available_mrp #: model:ir.model,name:stock_available_mrp.model_product_product msgid "Product" @@ -28,6 +40,13 @@ msgid "Product Template" msgstr "Tuotteen malli" #. module: stock_available_mrp -#: model:product.uom,name:stock_available_mrp.thousand -msgid "Thousand" +#: model:product.product,name:stock_available_mrp.product_kit_1a +#: model:product.template,name:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build Kit" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,description:stock_available_mrp.product_kit_1a +#: model:product.template,description:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build kit." msgstr "" diff --git a/stock_available_mrp/i18n/fr.po b/stock_available_mrp/i18n/fr.po index 0aaafddd3..b61ff2a0a 100644 --- a/stock_available_mrp/i18n/fr.po +++ b/stock_available_mrp/i18n/fr.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_available_mrp -# +# # Translators: msgid "" msgstr "" @@ -10,13 +10,25 @@ msgstr "" "POT-Creation-Date: 2016-01-14 01:38+0000\n" "PO-Revision-Date: 2016-01-13 16:35+0000\n" "Last-Translator: <>\n" -"Language-Team: French (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/fr/)\n" +"Language-Team: French (http://www.transifex.com/oca/OCA-stock-logistics-" +"warehouse-8-0/language/fr/)\n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" +#. module: stock_available_mrp +#: model:product.product,name:stock_available_mrp.product_product_9_white +#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template +msgid "Apple Wireless Keyboard" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id +msgid "Bill of Materials" +msgstr "" + #. module: stock_available_mrp #: model:ir.model,name:stock_available_mrp.model_product_product msgid "Product" @@ -28,6 +40,13 @@ msgid "Product Template" msgstr "Modèle de produit" #. module: stock_available_mrp -#: model:product.uom,name:stock_available_mrp.thousand -msgid "Thousand" +#: model:product.product,name:stock_available_mrp.product_kit_1a +#: model:product.template,name:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build Kit" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,description:stock_available_mrp.product_kit_1a +#: model:product.template,description:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build kit." msgstr "" diff --git a/stock_available_mrp/i18n/hr_HR.po b/stock_available_mrp/i18n/hr_HR.po index 59853430f..15a0b4f1f 100644 --- a/stock_available_mrp/i18n/hr_HR.po +++ b/stock_available_mrp/i18n/hr_HR.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_available_mrp -# +# # Translators: # Bole , 2016 msgid "" @@ -11,28 +11,25 @@ msgstr "" "POT-Creation-Date: 2016-06-12 19:59+0000\n" "PO-Revision-Date: 2016-06-14 10:45+0000\n" "Last-Translator: Bole \n" -"Language-Team: Croatian (Croatia) (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-9-0/language/hr_HR/)\n" +"Language-Team: Croatian (Croatia) (http://www.transifex.com/oca/OCA-stock-" +"logistics-warehouse-9-0/language/hr_HR/)\n" +"Language: hr_HR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: hr_HR\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #. module: stock_available_mrp -#: model:ir.ui.view,arch_db:stock_available_mrp.view_product_form_potential_qty -msgid "Potential" -msgstr "Potencijal" +#: model:product.product,name:stock_available_mrp.product_product_9_white +#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template +msgid "Apple Wireless Keyboard" +msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_component_ids -msgid "Component ids" -msgstr "ID-ovi komponenata" - -#. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_potential_qty -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_template_potential_qty -msgid "Potential" -msgstr "Potencijal" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id +msgid "Bill of Materials" +msgstr "" #. module: stock_available_mrp #: model:ir.model,name:stock_available_mrp.model_product_product @@ -45,21 +42,42 @@ msgid "Product Template" msgstr "Predložak proizvoda" #. module: stock_available_mrp -#: model:ir.model.fields,help:stock_available_mrp.field_product_product_potential_qty -msgid "" -"Quantity of this Product that could be produced using the materials already " -"at hand." -msgstr "Količina ovog proizvoda nije mogla biti proizvedena korištenjem trenutno raspoloživih materijala." +#: model:product.product,name:stock_available_mrp.product_kit_1a +#: model:product.template,name:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build Kit" +msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,help:stock_available_mrp.field_product_template_potential_qty -msgid "" -"Quantity of this Product that could be produced using the materials already " -"at hand. If the product has several variants, this will be the biggest " -"quantity that can be made for a any single variant." -msgstr "Količina ovog proizvoda koja može biti proizvodedna raspoloživim količinama sirovina. Ako proizvod ima nekoliko varijanti, ovo će biti najveća moguća količina koja se može proizvesti za svaku pojedinu varijantu." +#: model:product.product,description:stock_available_mrp.product_kit_1a +#: model:product.template,description:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build kit." +msgstr "" -#. module: stock_available_mrp -#: model:product.uom,name:stock_available_mrp.thousand -msgid "Thousand" -msgstr "Tisuću" +#~ msgid "Potential" +#~ msgstr "Potencijal" + +#~ msgid "Component ids" +#~ msgstr "ID-ovi komponenata" + +#~ msgid "Potential" +#~ msgstr "Potencijal" + +#~ msgid "" +#~ "Quantity of this Product that could be produced using the materials " +#~ "already at hand." +#~ msgstr "" +#~ "Količina ovog proizvoda nije mogla biti proizvedena korištenjem trenutno " +#~ "raspoloživih materijala." + +#~ msgid "" +#~ "Quantity of this Product that could be produced using the materials " +#~ "already at hand. If the product has several variants, this will be the " +#~ "biggest quantity that can be made for a any single variant." +#~ msgstr "" +#~ "Količina ovog proizvoda koja može biti proizvodedna raspoloživim " +#~ "količinama sirovina. Ako proizvod ima nekoliko varijanti, ovo će biti " +#~ "najveća moguća količina koja se može proizvesti za svaku pojedinu " +#~ "varijantu." + +#~ msgid "Thousand" +#~ msgstr "Tisuću" diff --git a/stock_available_mrp/i18n/it.po b/stock_available_mrp/i18n/it.po index bace38b97..24cf94c0d 100644 --- a/stock_available_mrp/i18n/it.po +++ b/stock_available_mrp/i18n/it.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_available_mrp -# +# # Translators: msgid "" msgstr "" @@ -10,27 +10,23 @@ msgstr "" "POT-Creation-Date: 2016-09-04 10:11+0000\n" "PO-Revision-Date: 2016-04-27 11:10+0000\n" "Last-Translator: <>\n" -"Language-Team: Italian (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-9-0/language/it/)\n" +"Language-Team: Italian (http://www.transifex.com/oca/OCA-stock-logistics-" +"warehouse-9-0/language/it/)\n" +"Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: stock_available_mrp -#: model:ir.ui.view,arch_db:stock_available_mrp.view_product_form_potential_qty -msgid "Potential" +#: model:product.product,name:stock_available_mrp.product_product_9_white +#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template +msgid "Apple Wireless Keyboard" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_component_ids -msgid "Component ids" -msgstr "" - -#. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_potential_qty -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_template_potential_qty -msgid "Potential" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id +msgid "Bill of Materials" msgstr "" #. module: stock_available_mrp @@ -44,21 +40,13 @@ msgid "Product Template" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,help:stock_available_mrp.field_product_product_potential_qty -msgid "" -"Quantity of this Product that could be produced using the materials already " -"at hand." +#: model:product.product,name:stock_available_mrp.product_kit_1a +#: model:product.template,name:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build Kit" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,help:stock_available_mrp.field_product_template_potential_qty -msgid "" -"Quantity of this Product that could be produced using the materials already " -"at hand. If the product has several variants, this will be the biggest " -"quantity that can be made for a any single variant." -msgstr "" - -#. module: stock_available_mrp -#: model:product.uom,name:stock_available_mrp.thousand -msgid "Thousand" +#: model:product.product,description:stock_available_mrp.product_kit_1a +#: model:product.template,description:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build kit." msgstr "" diff --git a/stock_available_mrp/i18n/pt_BR.po b/stock_available_mrp/i18n/pt_BR.po index de9e46c7a..d8c9dbdf6 100644 --- a/stock_available_mrp/i18n/pt_BR.po +++ b/stock_available_mrp/i18n/pt_BR.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_available_mrp -# +# # Translators: # Claudio Araujo Santos , 2016 msgid "" @@ -11,28 +11,24 @@ msgstr "" "POT-Creation-Date: 2016-07-03 20:56+0000\n" "PO-Revision-Date: 2016-07-07 19:41+0000\n" "Last-Translator: Claudio Araujo Santos \n" -"Language-Team: Portuguese (Brazil) (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-9-0/language/pt_BR/)\n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/oca/OCA-stock-" +"logistics-warehouse-9-0/language/pt_BR/)\n" +"Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: pt_BR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. module: stock_available_mrp -#: model:ir.ui.view,arch_db:stock_available_mrp.view_product_form_potential_qty -msgid "Potential" -msgstr "Potencial" +#: model:product.product,name:stock_available_mrp.product_product_9_white +#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template +msgid "Apple Wireless Keyboard" +msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_component_ids -msgid "Component ids" -msgstr "IDs de componentes" - -#. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_potential_qty -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_template_potential_qty -msgid "Potential" -msgstr "Potencial" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id +msgid "Bill of Materials" +msgstr "" #. module: stock_available_mrp #: model:ir.model,name:stock_available_mrp.model_product_product @@ -45,21 +41,41 @@ msgid "Product Template" msgstr "Modelo Produto" #. module: stock_available_mrp -#: model:ir.model.fields,help:stock_available_mrp.field_product_product_potential_qty -msgid "" -"Quantity of this Product that could be produced using the materials already " -"at hand." -msgstr "Quantidade deste produto que poderia ser produzido usando os materiais já na mão." +#: model:product.product,name:stock_available_mrp.product_kit_1a +#: model:product.template,name:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build Kit" +msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,help:stock_available_mrp.field_product_template_potential_qty -msgid "" -"Quantity of this Product that could be produced using the materials already " -"at hand. If the product has several variants, this will be the biggest " -"quantity that can be made for a any single variant." -msgstr "Quantidade deste produto que poderia ser produzido usando os materiais já na mão. Se o produto tiver várias variantes, esta será a maior quantidade que pode ser feito por qualquer uma única variante." +#: model:product.product,description:stock_available_mrp.product_kit_1a +#: model:product.template,description:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build kit." +msgstr "" -#. module: stock_available_mrp -#: model:product.uom,name:stock_available_mrp.thousand -msgid "Thousand" -msgstr "Mil" +#~ msgid "Potential" +#~ msgstr "Potencial" + +#~ msgid "Component ids" +#~ msgstr "IDs de componentes" + +#~ msgid "Potential" +#~ msgstr "Potencial" + +#~ msgid "" +#~ "Quantity of this Product that could be produced using the materials " +#~ "already at hand." +#~ msgstr "" +#~ "Quantidade deste produto que poderia ser produzido usando os materiais já " +#~ "na mão." + +#~ msgid "" +#~ "Quantity of this Product that could be produced using the materials " +#~ "already at hand. If the product has several variants, this will be the " +#~ "biggest quantity that can be made for a any single variant." +#~ msgstr "" +#~ "Quantidade deste produto que poderia ser produzido usando os materiais já " +#~ "na mão. Se o produto tiver várias variantes, esta será a maior quantidade " +#~ "que pode ser feito por qualquer uma única variante." + +#~ msgid "Thousand" +#~ msgstr "Mil" diff --git a/stock_available_mrp/i18n/sl.po b/stock_available_mrp/i18n/sl.po index 753889eea..096be0bde 100644 --- a/stock_available_mrp/i18n/sl.po +++ b/stock_available_mrp/i18n/sl.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_available_mrp -# +# # Translators: # Matjaž Mozetič , 2016 msgid "" @@ -11,12 +11,25 @@ msgstr "" "POT-Creation-Date: 2016-01-14 01:38+0000\n" "PO-Revision-Date: 2016-01-14 05:18+0000\n" "Last-Translator: Matjaž Mozetič \n" -"Language-Team: Slovenian (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-8-0/language/sl/)\n" +"Language-Team: Slovenian (http://www.transifex.com/oca/OCA-stock-logistics-" +"warehouse-8-0/language/sl/)\n" +"Language: sl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: sl\n" -"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n" +"%100==4 ? 2 : 3);\n" + +#. module: stock_available_mrp +#: model:product.product,name:stock_available_mrp.product_product_9_white +#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template +msgid "Apple Wireless Keyboard" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id +msgid "Bill of Materials" +msgstr "" #. module: stock_available_mrp #: model:ir.model,name:stock_available_mrp.model_product_product @@ -29,6 +42,16 @@ msgid "Product Template" msgstr "Predloga proizvoda" #. module: stock_available_mrp -#: model:product.uom,name:stock_available_mrp.thousand -msgid "Thousand" -msgstr "Tisoč" +#: model:product.product,name:stock_available_mrp.product_kit_1a +#: model:product.template,name:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build Kit" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,description:stock_available_mrp.product_kit_1a +#: model:product.template,description:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build kit." +msgstr "" + +#~ msgid "Thousand" +#~ msgstr "Tisoč" diff --git a/stock_available_mrp/i18n/stock_available_mrp.pot b/stock_available_mrp/i18n/stock_available_mrp.pot index ae3a04141..ff8ef8502 100644 --- a/stock_available_mrp/i18n/stock_available_mrp.pot +++ b/stock_available_mrp/i18n/stock_available_mrp.pot @@ -1,13 +1,11 @@ -# Translation of OpenERP Server. +# Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_available_mrp # msgid "" msgstr "" -"Project-Id-Version: OpenERP Server 7.0\n" +"Project-Id-Version: Odoo Server 11.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" @@ -16,18 +14,35 @@ msgstr "" "Plural-Forms: \n" #. module: stock_available_mrp -#: field:product.product,potential_qty:0 -msgid "Potential" +#: model:product.product,name:stock_available_mrp.product_product_9_white +#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template +msgid "Apple Wireless Keyboard" +msgstr "" + +#. module: stock_available_mrp +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id +msgid "Bill of Materials" 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." +#: model:ir.model,name:stock_available_mrp.model_product_template +msgid "Product Template" msgstr "" + +#. module: stock_available_mrp +#: model:product.product,name:stock_available_mrp.product_kit_1a +#: model:product.template,name:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build Kit" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,description:stock_available_mrp.product_kit_1a +#: model:product.template,description:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build kit." +msgstr "" + diff --git a/stock_available_mrp/i18n/zh_CN.po b/stock_available_mrp/i18n/zh_CN.po index f9a83998f..044142b9d 100644 --- a/stock_available_mrp/i18n/zh_CN.po +++ b/stock_available_mrp/i18n/zh_CN.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * stock_available_mrp -# +# # Translators: # Jeffery Chenn , 2016 msgid "" @@ -11,28 +11,24 @@ msgstr "" "POT-Creation-Date: 2016-08-28 10:00+0000\n" "PO-Revision-Date: 2016-09-04 06:06+0000\n" "Last-Translator: Jeffery Chenn \n" -"Language-Team: Chinese (China) (http://www.transifex.com/oca/OCA-stock-logistics-warehouse-9-0/language/zh_CN/)\n" +"Language-Team: Chinese (China) (http://www.transifex.com/oca/OCA-stock-" +"logistics-warehouse-9-0/language/zh_CN/)\n" +"Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" #. module: stock_available_mrp -#: model:ir.ui.view,arch_db:stock_available_mrp.view_product_form_potential_qty -msgid "Potential" -msgstr "潜在" - -#. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_component_ids -msgid "Component ids" +#: model:product.product,name:stock_available_mrp.product_product_9_white +#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template +msgid "Apple Wireless Keyboard" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_potential_qty -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_template_potential_qty -msgid "Potential" -msgstr "潜在" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id +msgid "Bill of Materials" +msgstr "" #. module: stock_available_mrp #: model:ir.model,name:stock_available_mrp.model_product_product @@ -45,21 +41,22 @@ msgid "Product Template" msgstr "产品模板" #. module: stock_available_mrp -#: model:ir.model.fields,help:stock_available_mrp.field_product_product_potential_qty -msgid "" -"Quantity of this Product that could be produced using the materials already " -"at hand." +#: model:product.product,name:stock_available_mrp.product_kit_1a +#: model:product.template,name:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build Kit" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,help:stock_available_mrp.field_product_template_potential_qty -msgid "" -"Quantity of this Product that could be produced using the materials already " -"at hand. If the product has several variants, this will be the biggest " -"quantity that can be made for a any single variant." +#: model:product.product,description:stock_available_mrp.product_kit_1a +#: model:product.template,description:stock_available_mrp.product_kit_1a_product_template +msgid "Self Build kit." msgstr "" -#. module: stock_available_mrp -#: model:product.uom,name:stock_available_mrp.thousand -msgid "Thousand" -msgstr "千" +#~ msgid "Potential" +#~ msgstr "潜在" + +#~ msgid "Potential" +#~ msgstr "潜在" + +#~ msgid "Thousand" +#~ msgstr "千" diff --git a/stock_available_mrp/models/__init__.py b/stock_available_mrp/models/__init__.py index defb121af..28a3f0d0a 100644 --- a/stock_available_mrp/models/__init__.py +++ b/stock_available_mrp/models/__init__.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -# © 2014 Numérigraphe SARL +# Copyright 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import product_product diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 57a3b1d4d..8798c3ffe 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -# © 2014 Numérigraphe SARL +# Copyright 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from collections import Counter @@ -62,7 +61,7 @@ class ProductProduct(models.Model): if not product_with_bom: return res, stock_dict icp = self.env['ir.config_parameter'] - stock_available_mrp_based_on = icp.get_param( + stock_available_mrp_based_on = icp.sudo().get_param( 'stock_available_mrp_based_on', 'qty_available' ) @@ -77,7 +76,7 @@ class ProductProduct(models.Model): # Compute stock for product components. # {'productid': {field_name: qty}} - if res and stock_available_mrp_based_on in res.values()[0]: + if res and stock_available_mrp_based_on in list(res.values())[0]: # If the qty is computed by the same method use it to avoid # stressing the cache component_qties, _ = \ @@ -104,12 +103,20 @@ class ProductProduct(models.Model): # Find the lowest quantity we can make with the stock at hand components_potential_qty = min( [component_qties[component.id][ - stock_available_mrp_based_on] // need + stock_available_mrp_based_on] / need for component, need in component_needs.items()] ) - potential_qty = (product.bom_id.product_qty * - components_potential_qty) + bom_id = product.bom_id + potential_qty = (bom_id.product_qty * components_potential_qty) + + # We want to respect the rounding factor of the potential_qty + # Rounding down as we want to be pesimistic. + potential_qty = bom_id.product_uom_id._compute_quantity( + potential_qty, + product.bom_id.product_tmpl_id.uom_id, + rounding_method='DOWN' + ) res[product.id]['potential_qty'] = potential_qty res[product.id]['immediately_usable_qty'] += potential_qty diff --git a/stock_available_mrp/models/product_template.py b/stock_available_mrp/models/product_template.py index 5ae46c3a5..dee7e6147 100644 --- a/stock_available_mrp/models/product_template.py +++ b/stock_available_mrp/models/product_template.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -# © 2014 Numérigraphe SARL +# Copyright 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import models, api diff --git a/stock_available_mrp/static/description/icon.png b/stock_available_mrp/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 zcmW++2RxMjAAjx~&dlBk9S+%}OXg)AGE&Cb*&}d0jUxM@u(PQx^-s)697TX`ehR4?GS^qbkof1cslKgkU)h65qZ9Oc=ml_0temigYLJfnz{IDzUf>bGs4N!v3=Z3jMq&A#7%rM5eQ#dc?k~! zVpnB`o+K7|Al`Q_U;eD$B zfJtP*jH`siUq~{KE)`jP2|#TUEFGRryE2`i0**z#*^6~AI|YzIWy$Cu#CSLW3q=GA z6`?GZymC;dCPk~rBS%eCb`5OLr;RUZ;D`}um=H)BfVIq%7VhiMr)_#G0N#zrNH|__ zc+blN2UAB0=617@>_u;MPHN;P;N#YoE=)R#i$k_`UAA>WWCcEVMh~L_ zj--gtp&|K1#58Yz*AHCTMziU1Jzt_jG0I@qAOHsk$2}yTmVkBp_eHuY$A9)>P6o~I z%aQ?!(GqeQ-Y+b0I(m9pwgi(IIZZzsbMv+9w{PFtd_<_(LA~0H(xz{=FhLB@(1&qHA5EJw1>>=%q2f&^X>IQ{!GJ4e9U z&KlB)z(84HmNgm2hg2C0>WM{E(DdPr+EeU_N@57;PC2&DmGFW_9kP&%?X4}+xWi)( z;)z%wI5>D4a*5XwD)P--sPkoY(a~WBw;E~AW`Yue4kFa^LM3X`8x|}ZUeMnqr}>kH zG%WWW>3ml$Yez?i%)2pbKPI7?5o?hydokgQyZsNEr{a|mLdt;X2TX(#B1j35xPnPW z*bMSSOauW>o;*=kO8ojw91VX!qoOQb)zHJ!odWB}d+*K?#sY_jqPdg{Sm2HdYzdEx zOGVPhVRTGPtv0o}RfVP;Nd(|CB)I;*t&QO8h zFfekr30S!-LHmV_Su-W+rEwYXJ^;6&3|L$mMC8*bQptyOo9;>Qb9Q9`ySe3%V$A*9 zeKEe+b0{#KWGp$F+tga)0RtI)nhMa-K@JS}2krK~n8vJ=Ngm?R!9G<~RyuU0d?nz# z-5EK$o(!F?hmX*2Yt6+coY`6jGbb7tF#6nHA zuKk=GGJ;ZwON1iAfG$E#Y7MnZVmrY|j0eVI(DN_MNFJmyZ|;w4tf@=CCDZ#5N_0K= z$;R~bbk?}TpfDjfB&aiQ$VA}s?P}xPERJG{kxk5~R`iRS(SK5d+Xs9swCozZISbnS zk!)I0>t=A<-^z(cmSFz3=jZ23u13X><0b)P)^1T_))Kr`e!-pb#q&J*Q`p+B6la%C zuVl&0duN<;uOsB3%T9Fp8t{ED108<+W(nOZd?gDnfNBC3>M8WE61$So|P zVvqH0SNtDTcsUdzaMDpT=Ty0pDHHNL@Z0w$Y`XO z2M-_r1S+GaH%pz#Uy0*w$Vdl=X=rQXEzO}d6J^R6zjM1u&c9vYLvLp?W7w(?np9x1 zE_0JSAJCPB%i7p*Wvg)pn5T`8k3-uR?*NT|J`eS#_#54p>!p(mLDvmc-3o0mX*mp_ zN*AeS<>#^-{S%W<*mz^!X$w_2dHWpcJ6^j64qFBft-o}o_Vx80o0>}Du;>kLts;$8 zC`7q$QI(dKYG`Wa8#wl@V4jVWBRGQ@1dr-hstpQL)Tl+aqVpGpbSfN>5i&QMXfiZ> zaA?T1VGe?rpQ@;+pkrVdd{klI&jVS@I5_iz!=UMpTsa~mBga?1r}aRBm1WS;TT*s0f0lY=JBl66Upy)-k4J}lh=P^8(SXk~0xW=T9v*B|gzIhN z>qsO7dFd~mgxAy4V?&)=5ieYq?zi?ZEoj)&2o)RLy=@hbCRcfT5jigwtQGE{L*8<@Yd{zg;CsL5mvzfDY}P-wos_6PfprFVaeqNE%h zKZhLtcQld;ZD+>=nqN~>GvROfueSzJD&BE*}XfU|H&(FssBqY=hPCt`d zH?@s2>I(|;fcW&YM6#V#!kUIP8$Nkdh0A(bEVj``-AAyYgwY~jB zT|I7Bf@%;7aL7Wf4dZ%VqF$eiaC38OV6oy3Z#TER2G+fOCd9Iaoy6aLYbPTN{XRPz z;U!V|vBf%H!}52L2gH_+j;`bTcQRXB+y9onc^wLm5wi3-Be}U>k_u>2Eg$=k!(l@I zcCg+flakT2Nej3i0yn+g+}%NYb?ta;R?(g5SnwsQ49U8Wng8d|{B+lyRcEDvR3+`O{zfmrmvFrL6acVP%yG98X zo&+VBg@px@i)%o?dG(`T;n*$S5*rnyiR#=wW}}GsAcfyQpE|>a{=$Hjg=-*_K;UtD z#z-)AXwSRY?OPefw^iI+ z)AXz#PfEjlwTes|_{sB?4(O@fg0AJ^g8gP}ex9Ucf*@_^J(s_5jJV}c)s$`Myn|Kd z$6>}#q^n{4vN@+Os$m7KV+`}c%4)4pv@06af4-x5#wj!KKb%caK{A&Y#Rfs z-po?Dcb1({W=6FKIUirH&(yg=*6aLCekcKwyfK^JN5{wcA3nhO(o}SK#!CINhI`-I z1)6&n7O&ZmyFMuNwvEic#IiOAwNkR=u5it{B9n2sAJV5pNhar=j5`*N!Na;c7g!l$ z3aYBqUkqqTJ=Re-;)s!EOeij=7SQZ3Hq}ZRds%IM*PtM$wV z@;rlc*NRK7i3y5BETSKuumEN`Xu_8GP1Ri=OKQ$@I^ko8>H6)4rjiG5{VBM>B|%`&&s^)jS|-_95&yc=GqjNo{zFkw%%HHhS~e=s zD#sfS+-?*t|J!+ozP6KvtOl!R)@@-z24}`9{QaVLD^9VCSR2b`b!KC#o;Ki<+wXB6 zx3&O0LOWcg4&rv4QG0)4yb}7BFSEg~=IR5#ZRj8kg}dS7_V&^%#Do==#`u zpy6{ox?jWuR(;pg+f@mT>#HGWHAJRRDDDv~@(IDw&R>9643kK#HN`!1vBJHnC+RM&yIh8{gG2q zA%e*U3|N0XSRa~oX-3EAneep)@{h2vvd3Xvy$7og(sayr@95+e6~Xvi1tUqnIxoIH zVWo*OwYElb#uyW{Imam6f2rGbjR!Y3`#gPqkv57dB6K^wRGxc9B(t|aYDGS=m$&S!NmCtrMMaUg(c zc2qC=2Z`EEFMW-me5B)24AqF*bV5Dr-M5ig(l-WPS%CgaPzs6p_gnCIvTJ=Y<6!gT zVt@AfYCzjjsMEGi=rDQHo0yc;HqoRNnNFeWZgcm?f;cp(6CNylj36DoL(?TS7eU#+ z7&mfr#y))+CJOXQKUMZ7QIdS9@#-}7y2K1{8)cCt0~-X0O!O?Qx#E4Og+;A2SjalQ zs7r?qn0H044=sDN$SRG$arw~n=+T_DNdSrarmu)V6@|?1-ZB#hRn`uilTGPJ@fqEy zGt(f0B+^JDP&f=r{#Y_wi#AVDf-y!RIXU^0jXsFpf>=Ji*TeqSY!H~AMbJdCGLhC) zn7Rx+sXw6uYj;WRYrLd^5IZq@6JI1C^YkgnedZEYy<&4(z%Q$5yv#Boo{AH8n$a zhb4Y3PWdr269&?V%uI$xMcUrMzl=;w<_nm*qr=c3Rl@i5wWB;e-`t7D&c-mcQl7x! zZWB`UGcw=Y2=}~wzrfLx=uet<;m3~=8I~ZRuzvMQUQdr+yTV|ATf1Uuomr__nDf=X zZ3WYJtHp_ri(}SQAPjv+Y+0=fH4krOP@S&=zZ-t1jW1o@}z;xk8 z(Nz1co&El^HK^NrhVHa-_;&88vTU>_J33=%{if;BEY*J#1n59=07jrGQ#IP>@u#3A z;!q+E1Rj3ZJ+!4bq9F8PXJ@yMgZL;>&gYA0%_Kbi8?S=XGM~dnQZQ!yBSgcZhY96H zrWnU;k)qy`rX&&xlDyA%(a1Hhi5CWkmg(`Gb%m(HKi-7Z!LKGRP_B8@`7&hdDy5n= z`OIxqxiVfX@OX1p(mQu>0Ai*v_cTMiw4qRt3~NBvr9oBy0)r>w3p~V0SCm=An6@3n)>@z!|o-$HvDK z|3D2ZMJkLE5loMKl6R^ez@Zz%S$&mbeoqH5`Bb){Ei21q&VP)hWS2tjShfFtGE+$z zzCR$P#uktu+#!w)cX!lWN1XU%K-r=s{|j?)Akf@q#3b#{6cZCuJ~gCxuMXRmI$nGtnH+-h z+GEi!*X=AP<|fG`1>MBdTb?28JYc=fGvAi2I<$B(rs$;eoJCyR6_bc~p!XR@O-+sD z=eH`-ye})I5ic1eL~TDmtfJ|8`0VJ*Yr=hNCd)G1p2MMz4C3^Mj?7;!w|Ly%JqmuW zlIEW^Ft%z?*|fpXda>Jr^1noFZEwFgVV%|*XhH@acv8rdGxeEX{M$(vG{Zw+x(ei@ zmfXb22}8-?Fi`vo-YVrTH*C?a8%M=Hv9MqVH7H^J$KsD?>!SFZ;ZsvnHr_gn=7acz z#W?0eCdVhVMWN12VV^$>WlQ?f;P^{(&pYTops|btm6aj>_Uz+hqpGwB)vWp0Cf5y< zft8-je~nn?W11plq}N)4A{l8I7$!ks_x$PXW-2XaRFswX_BnF{R#6YIwMhAgd5F9X zGmwdadS6(a^fjHtXg8=l?Rc0Sm%hk6E9!5cLVloEy4eh(=FwgP`)~I^5~pBEWo+F6 zSf2ncyMurJN91#cJTy_u8Y}@%!bq1RkGC~-bV@SXRd4F{R-*V`bS+6;W5vZ(&+I<9$;-V|eNfLa5n-6% z2(}&uGRF;p92eS*sE*oR$@pexaqr*meB)VhmIg@h{uzkk$9~qh#cHhw#>O%)b@+(| z^IQgqzuj~Sk(J;swEM-3TrJAPCq9k^^^`q{IItKBRXYe}e0Tdr=Huf7da3$l4PdpwWDop%^}n;dD#K4s#DYA8SHZ z&1!riV4W4R7R#C))JH1~axJ)RYnM$$lIR%6fIVA@zV{XVyx}C+a-Dt8Y9M)^KU0+H zR4IUb2CJ{Hg>CuaXtD50jB(_Tcx=Z$^WYu2u5kubqmwp%drJ6 z?Fo40g!Qd<-l=TQxqHEOuPX0;^z7iX?Ke^a%XT<13TA^5`4Xcw6D@Ur&VT&CUe0d} z1GjOVF1^L@>O)l@?bD~$wzgf(nxX1OGD8fEV?TdJcZc2KoUe|oP1#=$$7ee|xbY)A zDZq+cuTpc(fFdj^=!;{k03C69lMQ(|>uhRfRu%+!k&YOi-3|1QKB z z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zl&#s4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6# z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f# zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv! zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG z-wfS zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9 z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE# z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1 zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$ zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV( z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4 z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{ zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx} z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22 zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t< z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{} zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/stock_available_mrp/tests/__init__.py b/stock_available_mrp/tests/__init__.py index e6b40413f..4382bb886 100644 --- a/stock_available_mrp/tests/__init__.py +++ b/stock_available_mrp/tests/__init__.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -# © 2014 Numérigraphe SARL +# Copyright 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import test_potential_qty diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index 83cfba1ef..cdd16916f 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -# © 2014 Numérigraphe SARL +# Copyright 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo.tests.common import TransactionCase @@ -17,7 +16,14 @@ class TestPotentialQty(TransactionCase): self.bom_line_model = self.env["mrp.bom.line"] self.stock_quant_model = self.env["stock.quant"] self.config = self.env['ir.config_parameter'] + self.location = self.env['stock.location'] + # Get the warehouses + self.wh_main = self.browse_ref('stock.warehouse0') + self.wh_ch = self.browse_ref('stock.stock_warehouse_shop0') + # We need to compute parent_left and parent_right of the locations as + # they are used to compute qty_available of the product. + self.location._parent_store_compute() self.setup_demo_data() def setup_demo_data(self): @@ -26,9 +32,11 @@ class TestPotentialQty(TransactionCase): 'mrp.product_product_build_kit_product_template') #  First variant self.var1 = self.browse_ref('mrp.product_product_build_kit') + self.var1.type = 'product' #  Second variant self.var2 = self.browse_ref( 'stock_available_mrp.product_kit_1a') + self.var2.type = 'product' # Components that can be used to make the product component_ids = [ # KeyBoard @@ -41,15 +49,12 @@ class TestPotentialQty(TransactionCase): for component_id in ( component_ids + [v.id for v in self.tmpl.product_variant_ids]): - inventory = self.env['stock.inventory'].create( - {'name': 'no components: %s' % component_id, - 'location_id': self.ref('stock.stock_location_locations'), - 'filter': 'product', - 'product_id': component_id}) - inventory.prepare_inventory() - inventory.reset_real_qty() - inventory.action_done() + prod = self.product_model.browse(component_id) + self.env['stock.quant'].search([ + ('product_id', '=', prod.id) + ]).unlink() + self.product_model.invalidate_cache() #  A product without a BoM self.product_wo_bom = self.browse_ref('product.product_product_11') @@ -60,10 +65,6 @@ class TestPotentialQty(TransactionCase): self.var2, self.product_wo_bom]} - # Get the warehouses - self.wh_main = self.browse_ref('stock.warehouse0') - self.wh_ch = self.browse_ref('stock.stock_warehouse_shop0') - def create_inventory(self, product_id, qty, location_id=None): if location_id is None: location_id = self.wh_main.lot_stock_id.id @@ -73,8 +74,7 @@ class TestPotentialQty(TransactionCase): 'location_id': location_id, 'filter': 'partial' }) - inventory.prepare_inventory() - + inventory.action_start() self.env['stock.inventory.line'].create({ 'inventory_id': inventory.id, 'product_id': product_id, @@ -124,7 +124,7 @@ class TestPotentialQty(TransactionCase): 'company_id': chicago_id, 'location_id': self.wh_ch.lot_stock_id.id, 'filter': 'partial'}) - inventory.prepare_inventory() + inventory.action_start() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'company_id': chicago_id, @@ -139,7 +139,7 @@ class TestPotentialQty(TransactionCase): 'company_id': chicago_id, 'location_id': self.wh_ch.lot_stock_id.id, 'filter': 'partial'}) - inventory.prepare_inventory() + inventory.action_start() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'company_id': chicago_id, @@ -194,7 +194,7 @@ class TestPotentialQty(TransactionCase): {'name': 'Receive Mouses', 'location_id': self.wh_main.lot_stock_id.id, 'filter': 'partial'}) - inventory.prepare_inventory() + inventory.action_start() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'product_id': self.ref('product.product_product_12'), @@ -212,7 +212,7 @@ class TestPotentialQty(TransactionCase): {'name': 'components for 1st variant', 'location_id': self.wh_main.lot_stock_id.id, 'filter': 'partial'}) - inventory.prepare_inventory() + inventory.action_start() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'product_id': self.ref( @@ -236,7 +236,7 @@ class TestPotentialQty(TransactionCase): {'name': 'components for 2nd variant', 'location_id': self.wh_ch.lot_stock_id.id, 'filter': 'partial'}) - inventory.prepare_inventory() + inventory.action_start() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'product_id': self.ref('product.product_product_12'), @@ -279,17 +279,24 @@ class TestPotentialQty(TransactionCase): def test_multi_unit_recursive_bom(self): # Test multi-level and multi-units BOM - + uom_unit = self.env.ref('product.product_uom_unit') + uom_unit.rounding = 1.0 p1 = self.product_model.create({ 'name': 'Test product with BOM', + 'type': 'product', + 'uom_id': self.env.ref('product.product_uom_unit').id, }) p2 = self.product_model.create({ 'name': 'Test sub product with BOM', + 'type': 'product', + 'uom_id': self.env.ref('product.product_uom_unit').id, }) p3 = self.product_model.create({ - 'name': 'Test component' + 'name': 'Test component', + 'type': 'product', + 'uom_id': self.env.ref('product.product_uom_unit').id, }) bom_p1 = self.bom_model.create({ @@ -301,6 +308,8 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p1.id, 'product_id': p3.id, 'product_qty': 1, + 'product_uom_id': self.env.ref('product.product_uom_unit').id, + }) # Two p2 which have a bom @@ -308,6 +317,8 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p1.id, 'product_id': p2.id, 'product_qty': 2, + 'product_uom_id': self.env.ref('product.product_uom_unit').id, + }) bom_p2 = self.bom_model.create({ @@ -321,6 +332,8 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p2.id, 'product_id': p3.id, 'product_qty': 2, + 'product_uom_id': self.env.ref('product.product_uom_unit').id, + }) p1.refresh() @@ -329,7 +342,6 @@ class TestPotentialQty(TransactionCase): self.assertEqual(0, p1.potential_qty) self.create_inventory(p3.id, 1) - p1.refresh() self.assertEqual(0, p1.potential_qty) @@ -389,6 +401,7 @@ class TestPotentialQty(TransactionCase): # If iMac has a Bom and can be manufactured component = self.product_model.create({ 'name': 'component', + 'type': 'product' }) self.create_inventory(component.id, 5) @@ -415,14 +428,14 @@ class TestPotentialQty(TransactionCase): p1.refresh() self.assertEqual(5.0, p1.potential_qty) - def test_potential_qty__list(self): + def test_potential_qty_list(self): # Try to highlight a bug when _get_potential_qty is called on # a recordset with multiple products # Recursive compute is not working p1 = self.product_model.create({'name': 'Test P1'}) p2 = self.product_model.create({'name': 'Test P2'}) - p3 = self.product_model.create({'name': 'Test P3'}) + p3 = self.product_model.create({'name': 'Test P3', 'type': 'product'}) self.config.set_param('stock_available_mrp_based_on', 'immediately_usable_qty') From 779c73a3ae297a281d37db9f48f019ea29ff982e Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Sat, 6 Jul 2019 17:28:05 +0200 Subject: [PATCH 16/29] Migrate stock_available_mrp to v12 --- stock_available_mrp/__manifest__.py | 2 +- stock_available_mrp/demo/mrp_data.xml | 24 ++--- stock_available_mrp/models/product_product.py | 2 +- stock_available_mrp/readme/CONTRIBUTORS.rst | 5 + stock_available_mrp/readme/DESCRIPTION.rst | 6 ++ stock_available_mrp/readme/ROADMAP.rst | 24 +++++ .../tests/test_potential_qty.py | 96 ++++++++++--------- 7 files changed, 101 insertions(+), 58 deletions(-) create mode 100644 stock_available_mrp/readme/CONTRIBUTORS.rst create mode 100644 stock_available_mrp/readme/DESCRIPTION.rst create mode 100644 stock_available_mrp/readme/ROADMAP.rst diff --git a/stock_available_mrp/__manifest__.py b/stock_available_mrp/__manifest__.py index 58a507f11..2e3fe43f2 100644 --- a/stock_available_mrp/__manifest__.py +++ b/stock_available_mrp/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '11.0.1.0.0', + 'version': '12.0.1.0.0', "author": "Numérigraphe," "Odoo Community Association (OCA)", 'website': 'https://github.com/OCA/stock-logistics-warehouse', diff --git a/stock_available_mrp/demo/mrp_data.xml b/stock_available_mrp/demo/mrp_data.xml index a41a04f4c..1dc82fee9 100644 --- a/stock_available_mrp/demo/mrp_data.xml +++ b/stock_available_mrp/demo/mrp_data.xml @@ -3,11 +3,11 @@ PCSC234-WHITE + ref="mrp.product_product_table_kit_product_template"/> - + @@ -16,21 +16,21 @@ - - Apple Wireless Keyboard + + Bolt - 10.0 - 47.0 + 1.0 + 5.0 product - - - E-COM10-WHITE + + + BOLT-WHITE - - 1 - + + 4 + 5 diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 8798c3ffe..0cafb61e8 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -13,7 +13,7 @@ class ProductProduct(models.Model): bom_id = fields.Many2one( 'mrp.bom', compute='_compute_bom_id', - string='Bill of Materials' + string='BOM' ) @api.depends('virtual_available', 'bom_id', 'bom_id.product_qty') diff --git a/stock_available_mrp/readme/CONTRIBUTORS.rst b/stock_available_mrp/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..e274f35bc --- /dev/null +++ b/stock_available_mrp/readme/CONTRIBUTORS.rst @@ -0,0 +1,5 @@ +* Loïc Bellier (Numérigraphe) +* Lionel Sausin (Numérigraphe) +* many thanks to Graeme Gellatly for his advice and code review +* Laurent Mignon +* Cédric Pigeon diff --git a/stock_available_mrp/readme/DESCRIPTION.rst b/stock_available_mrp/readme/DESCRIPTION.rst new file mode 100644 index 000000000..83df836a5 --- /dev/null +++ b/stock_available_mrp/readme/DESCRIPTION.rst @@ -0,0 +1,6 @@ +This module takes the potential quantities available for Products into account in +the quantity available to promise, where the "Potential quantity" is the +quantity that can be manufactured with the components immediately at hand. +By configuration, the "Potential quantity" can be computed based on other product field. +For example, "Potential quantity" can be the quantity that can be manufactured +with the components available to promise. diff --git a/stock_available_mrp/readme/ROADMAP.rst b/stock_available_mrp/readme/ROADMAP.rst new file mode 100644 index 000000000..7283e16c0 --- /dev/null +++ b/stock_available_mrp/readme/ROADMAP.rst @@ -0,0 +1,24 @@ +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. + +As a consequence, and to avoid overestimating, **only the first level** of Bill of Materials is +considered. +However Sets (a.k.a "phantom" BoMs) are taken into account: if a component must be replaced with a set, it's the stock of the set's product which will decide the potential. + +If a product has several variants, only the variant with the biggest potential will be taken into account when reporting the production potential. +For example, even if you actually have enough components to make 10 iPads 16Go AND 42 iPads 32Go, we'll consider that you can promise only 42 iPads. + +Removed features +---------------- +Previous versions of this module used to let programmers demand to get the potential quantity in an arbitrary Unit of Measure using the `context`. This feature was present in the standard computations too until v8.0, but it has been dropped from the standard from v8.0 on. +For the sake of consistency the potential quantity is now always reported in the product's main Unit of Measure too. + +Roadmap +------- +Possible improvements for future versions: +* take manufacturing delays into account: we should not promise goods to customers if they want them delivered earlier that we can make them +* Compute the quantity of finished product that can be made directly on each Bill of Material: this would be useful for production managers, and may make the computations faster by avoiding to compute the same BoM several times when several variants share the same BoM +* 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. diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index cdd16916f..363ed7a44 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -29,20 +29,28 @@ class TestPotentialQty(TransactionCase): def setup_demo_data(self): #  An interesting product (multi-line BoM, variants) self.tmpl = self.browse_ref( - 'mrp.product_product_build_kit_product_template') + 'mrp.product_product_table_kit_product_template') #  First variant - self.var1 = self.browse_ref('mrp.product_product_build_kit') + self.var1 = self.browse_ref('mrp.product_product_table_kit') self.var1.type = 'product' #  Second variant self.var2 = self.browse_ref( 'stock_available_mrp.product_kit_1a') self.var2.type = 'product' + # Make bolt a stockable product to be able to change its stock + # we need to unreserve the existing move before being able to do it. + bolt = self.env.ref('mrp.product_product_computer_desk_bolt') + bolt_moves = self.env['stock.move'].search( + [('product_id', '=', bolt.id), + ('state', 'not in', ('done', 'cancel'))]) + bolt_moves._do_unreserve() + bolt.type = 'product' # Components that can be used to make the product component_ids = [ - # KeyBoard - self.ref('product.product_product_9'), - # Mouse - self.ref('product.product_product_12'), + # Bolt + bolt.id, + # Wood Panel + self.ref('mrp.product_product_wood_panel'), ] # Zero-out the inventory of all variants and components @@ -81,7 +89,7 @@ class TestPotentialQty(TransactionCase): 'location_id': location_id, 'product_qty': qty }) - inventory.action_done() + inventory._action_done() def create_simple_bom(self, product, sub_product, product_qty=1, sub_product_qty=1, @@ -118,7 +126,7 @@ class TestPotentialQty(TransactionCase): def test_potential_qty_no_bom_for_company(self): chicago_id = self.ref('stock.res_company_1') - # Receive 1000x CPUI5s owned by Chicago + # Receive 1000x Wood Panel owned by Chicago inventory = self.env['stock.inventory'].create( {'name': 'Receive CPUa8', 'company_id': chicago_id, @@ -128,12 +136,12 @@ class TestPotentialQty(TransactionCase): self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'company_id': chicago_id, - 'product_id': self.ref('product.product_product_9'), + 'product_id': self.ref('mrp.product_product_wood_panel'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) - inventory.action_done() + inventory._action_done() - # Put RAM-SR5 owned by Chicago for 1000x the 1st variant in main WH + # Put Bolt owned by Chicago for 1000x the 1st variant in main WH inventory = self.env['stock.inventory'].create( {'name': 'components for 1st variant', 'company_id': chicago_id, @@ -143,12 +151,12 @@ class TestPotentialQty(TransactionCase): self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'company_id': chicago_id, - 'product_id': self.ref('product.product_product_12'), + 'product_id': self.ref('mrp.product_product_computer_desk_bolt'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) - inventory.action_done() + inventory._action_done() self.assertPotentialQty( - self.tmpl, 1000.0, + self.tmpl, 250.0, "Wrong template potential after receiving components") test_user = self.env['res.users'].create( @@ -164,7 +172,7 @@ class TestPotentialQty(TransactionCase): test_user_tmpl = self.tmpl.sudo(test_user) self.assertPotentialQty( - test_user_tmpl, 1000.0, + test_user_tmpl, 250.0, "Simple user can access to the potential_qty") # Set the bom on the main company (visible to members of main company) @@ -181,7 +189,7 @@ class TestPotentialQty(TransactionCase): "company or company child of the bom's company") bom.company_id = chicago_id self.assertPotentialQty( - test_user_tmpl, 1000.0, '') + test_user_tmpl, 250.0, '') def test_potential_qty(self): for i in [self.tmpl, self.var1, self.var2]: @@ -189,7 +197,7 @@ class TestPotentialQty(TransactionCase): i, 0.0, "The potential quantity should start at 0") - # Receive 1000x Mouses + # Receive 1000x Wood Panel inventory = self.env['stock.inventory'].create( {'name': 'Receive Mouses', 'location_id': self.wh_main.lot_stock_id.id, @@ -197,17 +205,17 @@ class TestPotentialQty(TransactionCase): inventory.action_start() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_12'), + 'product_id': self.ref('mrp.product_product_wood_panel'), 'location_id': self.wh_main.lot_stock_id.id, 'product_qty': 1000.0}) - inventory.action_done() + inventory._action_done() for i in [self.tmpl, self.var1, self.var2]: self.assertPotentialQty( i, 0.0, "Receiving a single component should not change the " "potential of %s" % i) - # Receive enough keyboard to make 1000x the 1st variant in main WH + # Receive enough bolt to make 1000x the 1st variant in main WH inventory = self.env['stock.inventory'].create( {'name': 'components for 1st variant', 'location_id': self.wh_main.lot_stock_id.id, @@ -216,22 +224,22 @@ class TestPotentialQty(TransactionCase): self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'product_id': self.ref( - 'product.product_product_9'), + 'mrp.product_product_computer_desk_bolt'), 'location_id': self.wh_main.lot_stock_id.id, 'product_qty': 1000.0}) - inventory.action_done() + inventory._action_done() self.assertPotentialQty( - self.tmpl, 1000.0, + self.tmpl, 250.0, "Wrong template potential after receiving components") self.assertPotentialQty( - self.var1, 1000.0, + self.var1, 250.0, "Wrong variant 1 potential after receiving components") self.assertPotentialQty( self.var2, 0.0, "Receiving variant 1's component should not change " "variant 2's potential") - # Receive enough components to make 313 the 2nd variant at Chicago + # Receive enough components to make 213 the 2nd variant at Chicago inventory = self.env['stock.inventory'].create( {'name': 'components for 2nd variant', 'location_id': self.wh_ch.lot_stock_id.id, @@ -239,64 +247,64 @@ class TestPotentialQty(TransactionCase): inventory.action_start() self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, - 'product_id': self.ref('product.product_product_12'), + 'product_id': self.ref('mrp.product_product_wood_panel'), 'location_id': self.wh_ch.lot_stock_id.id, 'product_qty': 1000.0}) self.env['stock.inventory.line'].create( {'inventory_id': inventory.id, 'product_id': self.ref( - 'stock_available_mrp.product_product_9_white'), + 'stock_available_mrp.product_computer_desk_bolt_white'), 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 313.0}) - inventory.action_done() + 'product_qty': 852.0}) + inventory._action_done() self.assertPotentialQty( - self.tmpl, 1000.0, + self.tmpl.with_context(test=True), 250.0, "Wrong template potential after receiving components") self.assertPotentialQty( - self.var1, 1000.0, + self.var1, 250.0, "Receiving variant 2's component should not change " "variant 1's potential") self.assertPotentialQty( - self.var2, 313.0, + self.var2, 213.0, "Wrong variant 2 potential after receiving components") # Check by warehouse self.assertPotentialQty( - self.tmpl.with_context(warehouse=self.wh_main.id), 1000.0, + self.tmpl.with_context(warehouse=self.wh_main.id), 250.0, "Wrong potential quantity in main WH") self.assertPotentialQty( - self.tmpl.with_context(warehouse=self.wh_ch.id), 313.0, + self.tmpl.with_context(warehouse=self.wh_ch.id), 213.0, "Wrong potential quantity in Chicago WH") # Check by location self.assertPotentialQty( self.tmpl.with_context( - location=self.wh_main.lot_stock_id.id), 1000.0, + location=self.wh_main.lot_stock_id.id), 250.0, "Wrong potential quantity in main WH location") self.assertPotentialQty( self.tmpl.with_context( location=self.wh_ch.lot_stock_id.id), - 313.0, + 213.0, "Wrong potential quantity in Chicago WH location") def test_multi_unit_recursive_bom(self): # Test multi-level and multi-units BOM - uom_unit = self.env.ref('product.product_uom_unit') + uom_unit = self.env.ref('uom.product_uom_unit') uom_unit.rounding = 1.0 p1 = self.product_model.create({ 'name': 'Test product with BOM', 'type': 'product', - 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_id': self.env.ref('uom.product_uom_unit').id, }) p2 = self.product_model.create({ 'name': 'Test sub product with BOM', 'type': 'product', - 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_id': self.env.ref('uom.product_uom_unit').id, }) p3 = self.product_model.create({ 'name': 'Test component', 'type': 'product', - 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_id': self.env.ref('uom.product_uom_unit').id, }) bom_p1 = self.bom_model.create({ @@ -308,7 +316,7 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p1.id, 'product_id': p3.id, 'product_qty': 1, - 'product_uom_id': self.env.ref('product.product_uom_unit').id, + 'product_uom_id': self.env.ref('uom.product_uom_unit').id, }) @@ -317,7 +325,7 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p1.id, 'product_id': p2.id, 'product_qty': 2, - 'product_uom_id': self.env.ref('product.product_uom_unit').id, + 'product_uom_id': self.env.ref('uom.product_uom_unit').id, }) @@ -332,7 +340,7 @@ class TestPotentialQty(TransactionCase): 'bom_id': bom_p2.id, 'product_id': p3.id, 'product_qty': 2, - 'product_uom_id': self.env.ref('product.product_uom_unit').id, + 'product_uom_id': self.env.ref('uom.product_uom_unit').id, }) @@ -365,7 +373,7 @@ class TestPotentialQty(TransactionCase): # Test to change component stock for compute BOM stock # Get a demo product with outgoing move (qty: 3) - prod = self.browse_ref('product.product_product_20') + prod = self.browse_ref('product.product_product_16') # Set on hand qty self.create_inventory(prod.id, 3) From 047485dbbb8a6e6334e1418f1978e76be157f0d5 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Sat, 6 Jul 2019 17:31:36 +0200 Subject: [PATCH 17/29] Make stock_available_mrp compatible with other modules like stock_available_immediately --- stock_available_mrp/models/__init__.py | 1 - stock_available_mrp/models/product_product.py | 1 + stock_available_mrp/models/product_template.py | 16 ---------------- stock_available_mrp/readme/CONTRIBUTORS.rst | 1 + 4 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 stock_available_mrp/models/product_template.py diff --git a/stock_available_mrp/models/__init__.py b/stock_available_mrp/models/__init__.py index 28a3f0d0a..bf91af2eb 100644 --- a/stock_available_mrp/models/__init__.py +++ b/stock_available_mrp/models/__init__.py @@ -2,4 +2,3 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import product_product -from . import product_template diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 0cafb61e8..aa7111ca9 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -109,6 +109,7 @@ class ProductProduct(models.Model): bom_id = product.bom_id potential_qty = (bom_id.product_qty * components_potential_qty) + potential_qty = potential_qty > 0.0 and potential_qty or 0.0 # We want to respect the rounding factor of the potential_qty # Rounding down as we want to be pesimistic. diff --git a/stock_available_mrp/models/product_template.py b/stock_available_mrp/models/product_template.py deleted file mode 100644 index dee7e6147..000000000 --- a/stock_available_mrp/models/product_template.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2014 Numérigraphe SARL -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import models, api - - -class ProductTemplate(models.Model): - _inherit = 'product.template' - - @api.multi - def _compute_available_quantities_dict(self): - res = super(ProductTemplate, self)._compute_available_quantities_dict() - for template in self.filtered('bom_ids'): - res[template.id]['immediately_usable_qty'] =\ - template.virtual_available + res[template.id]['potential_qty'] - return res diff --git a/stock_available_mrp/readme/CONTRIBUTORS.rst b/stock_available_mrp/readme/CONTRIBUTORS.rst index e274f35bc..4f314834d 100644 --- a/stock_available_mrp/readme/CONTRIBUTORS.rst +++ b/stock_available_mrp/readme/CONTRIBUTORS.rst @@ -3,3 +3,4 @@ * many thanks to Graeme Gellatly for his advice and code review * Laurent Mignon * Cédric Pigeon +* Florian da Costa From 2bde95c1d4a4f3ef08e0461905109bbf016d9bd4 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Sat, 31 Aug 2019 09:30:11 +0200 Subject: [PATCH 18/29] [FIX] stock_available_mrp: Fix RST formatting + generate README --- stock_available_mrp/README.rst | 113 +++-- stock_available_mrp/readme/ROADMAP.rst | 43 +- .../static/description/index.html | 477 ++++++++++++++++++ 3 files changed, 584 insertions(+), 49 deletions(-) create mode 100644 stock_available_mrp/static/description/index.html diff --git a/stock_available_mrp/README.rst b/stock_available_mrp/README.rst index d0e8733f5..0f9265f28 100644 --- a/stock_available_mrp/README.rst +++ b/stock_available_mrp/README.rst @@ -1,11 +1,30 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 - ========================================================= Consider the production potential is available to promise ========================================================= +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/12.0/stock_available_mrp + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-12-0/stock-logistics-warehouse-12-0-stock_available_mrp + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/153/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + This module takes the potential quantities available for Products into account in the quantity available to promise, where the "Potential quantity" is the quantity that can be manufactured with the components immediately at hand. @@ -13,75 +32,97 @@ By configuration, the "Potential quantity" can be computed based on other produc For example, "Potential quantity" can be the quantity that can be manufactured with the components available to promise. -Usage -===== +**Table of contents** -.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas - :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/153/8.0 +.. contents:: + :local: Known issues / Roadmap ====================== 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. -As a consequence, and to avoid overestimating, **only the first level** of Bill of Materials is -considered. -However Sets (a.k.a "phantom" BoMs) are taken into account: if a component must be replaced with a set, it's the stock of the set's product which will decide the potential. +As a consequence, and to avoid overestimating, **only the first level** of Bill +of Materials is considered. -If a product has several variants, only the variant with the biggest potential will be taken into account when reporting the production potential. -For example, even if you actually have enough components to make 10 iPads 16Go AND 42 iPads 32Go, we'll consider that you can promise only 42 iPads. +However Sets (a.k.a "phantom" BoMs) are taken into account: if a component must +be replaced with a set, it's the stock of the set's product which will decide +the potential. + +If a product has several variants, only the variant with the biggest potential +will be taken into account when reporting the production potential. For +example, even if you actually have enough components to make 10 iPads 16Go AND +42 iPads 32Go, we'll consider that you can promise only 42 iPads. Removed features ----------------- -Previous versions of this module used to let programmers demand to get the potential quantity in an arbitrary Unit of Measure using the `context`. This feature was present in the standard computations too until v8.0, but it has been dropped from the standard from v8.0 on. -For the sake of consistency the potential quantity is now always reported in the product's main Unit of Measure too. +~~~~~~~~~~~~~~~~ +Previous versions of this module used to let programmers demand to get the +potential quantity in an arbitrary Unit of Measure using the `context`. This +feature was present in the standard computations too until v8.0, but it has +been dropped from the standard from v8.0 on. + +For the sake of consistency the potential quantity is now always reported in +the product's main Unit of Measure too. Roadmap -------- +~~~~~~~ Possible improvements for future versions: -* take manufacturing delays into account: we should not promise goods to customers if they want them delivered earlier that we can make them -* Compute the quantity of finished product that can be made directly on each Bill of Material: this would be useful for production managers, and may make the computations faster by avoiding to compute the same BoM several times when several variants share the same BoM -* 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. + +* Take manufacturing delays into account: we should not promise goods to + customers if they want them delivered earlier that we can make them +* Compute the quantity of finished product that can be made directly on each + Bill of Material: this would be useful for production managers, and may make + the computations faster by avoiding to compute the same BoM several times + when several variants share the same BoM. +* 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. Bug Tracker =========== -Bugs are tracked on `GitHub Issues -`_. In case of trouble, please -check there if your issue has already been reported. If you spotted it first, -help us smashing it by providing a detailed and welcomed `feedback -`_. +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. Credits ======= +Authors +~~~~~~~ + +* Numérigraphe + Contributors ------------- +~~~~~~~~~~~~ + * Loïc Bellier (Numérigraphe) * Lionel Sausin (Numérigraphe) * many thanks to Graeme Gellatly for his advice and code review * Laurent Mignon * Cédric Pigeon +* Florian da Costa -Maintainer ----------- +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. .. image:: https://odoo-community.org/logo.png :alt: Odoo Community Association :target: https://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. +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_available_mrp/readme/ROADMAP.rst b/stock_available_mrp/readme/ROADMAP.rst index 7283e16c0..88ebb1f2e 100644 --- a/stock_available_mrp/readme/ROADMAP.rst +++ b/stock_available_mrp/readme/ROADMAP.rst @@ -1,24 +1,41 @@ 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. -As a consequence, and to avoid overestimating, **only the first level** of Bill of Materials is -considered. -However Sets (a.k.a "phantom" BoMs) are taken into account: if a component must be replaced with a set, it's the stock of the set's product which will decide the potential. +As a consequence, and to avoid overestimating, **only the first level** of Bill +of Materials is considered. -If a product has several variants, only the variant with the biggest potential will be taken into account when reporting the production potential. -For example, even if you actually have enough components to make 10 iPads 16Go AND 42 iPads 32Go, we'll consider that you can promise only 42 iPads. +However Sets (a.k.a "phantom" BoMs) are taken into account: if a component must +be replaced with a set, it's the stock of the set's product which will decide +the potential. + +If a product has several variants, only the variant with the biggest potential +will be taken into account when reporting the production potential. For +example, even if you actually have enough components to make 10 iPads 16Go AND +42 iPads 32Go, we'll consider that you can promise only 42 iPads. Removed features ----------------- -Previous versions of this module used to let programmers demand to get the potential quantity in an arbitrary Unit of Measure using the `context`. This feature was present in the standard computations too until v8.0, but it has been dropped from the standard from v8.0 on. -For the sake of consistency the potential quantity is now always reported in the product's main Unit of Measure too. +~~~~~~~~~~~~~~~~ +Previous versions of this module used to let programmers demand to get the +potential quantity in an arbitrary Unit of Measure using the `context`. This +feature was present in the standard computations too until v8.0, but it has +been dropped from the standard from v8.0 on. + +For the sake of consistency the potential quantity is now always reported in +the product's main Unit of Measure too. Roadmap -------- +~~~~~~~ Possible improvements for future versions: -* take manufacturing delays into account: we should not promise goods to customers if they want them delivered earlier that we can make them -* Compute the quantity of finished product that can be made directly on each Bill of Material: this would be useful for production managers, and may make the computations faster by avoiding to compute the same BoM several times when several variants share the same BoM -* 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. + +* Take manufacturing delays into account: we should not promise goods to + customers if they want them delivered earlier that we can make them +* Compute the quantity of finished product that can be made directly on each + Bill of Material: this would be useful for production managers, and may make + the computations faster by avoiding to compute the same BoM several times + when several variants share the same BoM. +* 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. diff --git a/stock_available_mrp/static/description/index.html b/stock_available_mrp/static/description/index.html new file mode 100644 index 000000000..7e01b1be9 --- /dev/null +++ b/stock_available_mrp/static/description/index.html @@ -0,0 +1,477 @@ + + + + + + +Consider the production potential is available to promise + + + +
+

Consider the production potential is available to promise

+ + +

Beta License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

+

This module takes the potential quantities available for Products into account in +the quantity available to promise, where the “Potential quantity” is the +quantity that can be manufactured with the components immediately at hand. +By configuration, the “Potential quantity” can be computed based on other product field. +For example, “Potential quantity” can be the quantity that can be manufactured +with the components available to promise.

+

Table of contents

+ +
+

Known issues / Roadmap

+
+

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.

+

As a consequence, and to avoid overestimating, only the first level of Bill +of Materials is considered.

+

However Sets (a.k.a “phantom” BoMs) are taken into account: if a component must +be replaced with a set, it’s the stock of the set’s product which will decide +the potential.

+

If a product has several variants, only the variant with the biggest potential +will be taken into account when reporting the production potential. For +example, even if you actually have enough components to make 10 iPads 16Go AND +42 iPads 32Go, we’ll consider that you can promise only 42 iPads.

+
+
+

Removed features

+

Previous versions of this module used to let programmers demand to get the +potential quantity in an arbitrary Unit of Measure using the context. This +feature was present in the standard computations too until v8.0, but it has +been dropped from the standard from v8.0 on.

+

For the sake of consistency the potential quantity is now always reported in +the product’s main Unit of Measure too.

+
+
+

Roadmap

+

Possible improvements for future versions:

+
    +
  • Take manufacturing delays into account: we should not promise goods to +customers if they want them delivered earlier that we can make them
  • +
  • Compute the quantity of finished product that can be made directly on each +Bill of Material: this would be useful for production managers, and may make +the computations faster by avoiding to compute the same BoM several times +when several variants share the same BoM.
  • +
  • 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.
  • +
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Numérigraphe
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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.

+

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + From d76a2d7fccb0f90b449ec8f16bb2200b3bf24b34 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Sat, 31 Aug 2019 08:09:17 +0000 Subject: [PATCH 19/29] [UPD] Update stock_available_mrp.pot --- .../i18n/stock_available_mrp.pot | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/stock_available_mrp/i18n/stock_available_mrp.pot b/stock_available_mrp/i18n/stock_available_mrp.pot index ff8ef8502..3ea2293db 100644 --- a/stock_available_mrp/i18n/stock_available_mrp.pot +++ b/stock_available_mrp/i18n/stock_available_mrp.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 11.0\n" +"Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: <>\n" "Language-Team: \n" @@ -14,14 +14,14 @@ msgstr "" "Plural-Forms: \n" #. module: stock_available_mrp -#: model:product.product,name:stock_available_mrp.product_product_9_white -#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template -msgid "Apple Wireless Keyboard" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id +msgid "BOM" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id -msgid "Bill of Materials" +#: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template +msgid "Bolt" msgstr "" #. module: stock_available_mrp @@ -29,20 +29,31 @@ msgstr "" msgid "Product" msgstr "" -#. module: stock_available_mrp -#: model:ir.model,name:stock_available_mrp.model_product_template -msgid "Product Template" -msgstr "" - #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build Kit" +msgid "Table Kit" msgstr "" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build kit." +msgid "Table kit" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "Unit(s)" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,weight_uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "kg" msgstr "" From 4c5e538d463f57e04616a64f240cac2ae0abf966 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sat, 31 Aug 2019 09:56:38 +0000 Subject: [PATCH 20/29] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: stock-logistics-warehouse-12.0/stock-logistics-warehouse-12.0-stock_available_mrp Translate-URL: https://translation.odoo-community.org/projects/stock-logistics-warehouse-12-0/stock-logistics-warehouse-12-0-stock_available_mrp/ --- stock_available_mrp/i18n/de.po | 38 +++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/stock_available_mrp/i18n/de.po b/stock_available_mrp/i18n/de.po index deb92b8bd..a38fea285 100644 --- a/stock_available_mrp/i18n/de.po +++ b/stock_available_mrp/i18n/de.po @@ -20,14 +20,14 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: stock_available_mrp -#: model:product.product,name:stock_available_mrp.product_product_9_white -#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template -msgid "Apple Wireless Keyboard" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id +msgid "BOM" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id -msgid "Bill of Materials" +#: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template +msgid "Bolt" msgstr "" #. module: stock_available_mrp @@ -35,22 +35,36 @@ msgstr "" msgid "Product" msgstr "Produkt" -#. module: stock_available_mrp -#: model:ir.model,name:stock_available_mrp.model_product_template -msgid "Product Template" -msgstr "Produktvorlage" - #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build Kit" +msgid "Table Kit" msgstr "" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build kit." +msgid "Table kit" msgstr "" +#. module: stock_available_mrp +#: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "Unit(s)" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,weight_uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "kg" +msgstr "" + +#~ msgid "Product Template" +#~ msgstr "Produktvorlage" + #~ msgid "Thousand" #~ msgstr "Tausend" From 6192457f376c1b5a683b47fb723eb95a5dc0f883 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Tue, 3 Sep 2019 11:30:04 +0000 Subject: [PATCH 21/29] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: stock-logistics-warehouse-12.0/stock-logistics-warehouse-12.0-stock_available_mrp Translate-URL: https://translation.odoo-community.org/projects/stock-logistics-warehouse-12-0/stock-logistics-warehouse-12-0-stock_available_mrp/ --- stock_available_mrp/i18n/es.po | 38 +++++++++++++++++++++---------- stock_available_mrp/i18n/fi.po | 38 +++++++++++++++++++++---------- stock_available_mrp/i18n/fr.po | 38 +++++++++++++++++++++---------- stock_available_mrp/i18n/hr_HR.po | 38 +++++++++++++++++++++---------- stock_available_mrp/i18n/it.po | 35 ++++++++++++++++++---------- stock_available_mrp/i18n/pt_BR.po | 38 +++++++++++++++++++++---------- stock_available_mrp/i18n/sl.po | 38 +++++++++++++++++++++---------- stock_available_mrp/i18n/zh_CN.po | 38 +++++++++++++++++++++---------- 8 files changed, 205 insertions(+), 96 deletions(-) diff --git a/stock_available_mrp/i18n/es.po b/stock_available_mrp/i18n/es.po index 1eb112127..07d39842f 100644 --- a/stock_available_mrp/i18n/es.po +++ b/stock_available_mrp/i18n/es.po @@ -19,14 +19,14 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: stock_available_mrp -#: model:product.product,name:stock_available_mrp.product_product_9_white -#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template -msgid "Apple Wireless Keyboard" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id +msgid "BOM" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id -msgid "Bill of Materials" +#: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template +msgid "Bolt" msgstr "" #. module: stock_available_mrp @@ -34,19 +34,33 @@ msgstr "" msgid "Product" msgstr "Producto" -#. module: stock_available_mrp -#: model:ir.model,name:stock_available_mrp.model_product_template -msgid "Product Template" -msgstr "Plantilla de producto" - #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build Kit" +msgid "Table Kit" msgstr "" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build kit." +msgid "Table kit" msgstr "" + +#. module: stock_available_mrp +#: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "Unit(s)" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,weight_uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "kg" +msgstr "" + +#~ msgid "Product Template" +#~ msgstr "Plantilla de producto" diff --git a/stock_available_mrp/i18n/fi.po b/stock_available_mrp/i18n/fi.po index 8c6389aee..7c821407d 100644 --- a/stock_available_mrp/i18n/fi.po +++ b/stock_available_mrp/i18n/fi.po @@ -19,14 +19,14 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: stock_available_mrp -#: model:product.product,name:stock_available_mrp.product_product_9_white -#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template -msgid "Apple Wireless Keyboard" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id +msgid "BOM" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id -msgid "Bill of Materials" +#: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template +msgid "Bolt" msgstr "" #. module: stock_available_mrp @@ -34,19 +34,33 @@ msgstr "" msgid "Product" msgstr "Tuote" -#. module: stock_available_mrp -#: model:ir.model,name:stock_available_mrp.model_product_template -msgid "Product Template" -msgstr "Tuotteen malli" - #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build Kit" +msgid "Table Kit" msgstr "" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build kit." +msgid "Table kit" msgstr "" + +#. module: stock_available_mrp +#: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "Unit(s)" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,weight_uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "kg" +msgstr "" + +#~ msgid "Product Template" +#~ msgstr "Tuotteen malli" diff --git a/stock_available_mrp/i18n/fr.po b/stock_available_mrp/i18n/fr.po index b61ff2a0a..fdfffdc24 100644 --- a/stock_available_mrp/i18n/fr.po +++ b/stock_available_mrp/i18n/fr.po @@ -19,14 +19,14 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. module: stock_available_mrp -#: model:product.product,name:stock_available_mrp.product_product_9_white -#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template -msgid "Apple Wireless Keyboard" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id +msgid "BOM" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id -msgid "Bill of Materials" +#: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template +msgid "Bolt" msgstr "" #. module: stock_available_mrp @@ -34,19 +34,33 @@ msgstr "" msgid "Product" msgstr "Article" -#. module: stock_available_mrp -#: model:ir.model,name:stock_available_mrp.model_product_template -msgid "Product Template" -msgstr "Modèle de produit" - #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build Kit" +msgid "Table Kit" msgstr "" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build kit." +msgid "Table kit" msgstr "" + +#. module: stock_available_mrp +#: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "Unit(s)" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,weight_uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "kg" +msgstr "" + +#~ msgid "Product Template" +#~ msgstr "Modèle de produit" diff --git a/stock_available_mrp/i18n/hr_HR.po b/stock_available_mrp/i18n/hr_HR.po index 15a0b4f1f..c81966fc3 100644 --- a/stock_available_mrp/i18n/hr_HR.po +++ b/stock_available_mrp/i18n/hr_HR.po @@ -21,14 +21,14 @@ msgstr "" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #. module: stock_available_mrp -#: model:product.product,name:stock_available_mrp.product_product_9_white -#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template -msgid "Apple Wireless Keyboard" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id +msgid "BOM" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id -msgid "Bill of Materials" +#: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template +msgid "Bolt" msgstr "" #. module: stock_available_mrp @@ -36,23 +36,37 @@ msgstr "" msgid "Product" msgstr "Proizvod" -#. module: stock_available_mrp -#: model:ir.model,name:stock_available_mrp.model_product_template -msgid "Product Template" -msgstr "Predložak proizvoda" - #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build Kit" +msgid "Table Kit" msgstr "" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build kit." +msgid "Table kit" msgstr "" +#. module: stock_available_mrp +#: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "Unit(s)" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,weight_uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "kg" +msgstr "" + +#~ msgid "Product Template" +#~ msgstr "Predložak proizvoda" + #~ msgid "Potential" #~ msgstr "Potencijal" diff --git a/stock_available_mrp/i18n/it.po b/stock_available_mrp/i18n/it.po index 24cf94c0d..ecf95c695 100644 --- a/stock_available_mrp/i18n/it.po +++ b/stock_available_mrp/i18n/it.po @@ -19,14 +19,14 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: stock_available_mrp -#: model:product.product,name:stock_available_mrp.product_product_9_white -#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template -msgid "Apple Wireless Keyboard" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id +msgid "BOM" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id -msgid "Bill of Materials" +#: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template +msgid "Bolt" msgstr "" #. module: stock_available_mrp @@ -34,19 +34,30 @@ msgstr "" msgid "Product" msgstr "Prodotto" -#. module: stock_available_mrp -#: model:ir.model,name:stock_available_mrp.model_product_template -msgid "Product Template" -msgstr "" - #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build Kit" +msgid "Table Kit" msgstr "" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build kit." +msgid "Table kit" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "Unit(s)" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,weight_uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "kg" msgstr "" diff --git a/stock_available_mrp/i18n/pt_BR.po b/stock_available_mrp/i18n/pt_BR.po index d8c9dbdf6..c7257894b 100644 --- a/stock_available_mrp/i18n/pt_BR.po +++ b/stock_available_mrp/i18n/pt_BR.po @@ -20,14 +20,14 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. module: stock_available_mrp -#: model:product.product,name:stock_available_mrp.product_product_9_white -#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template -msgid "Apple Wireless Keyboard" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id +msgid "BOM" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id -msgid "Bill of Materials" +#: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template +msgid "Bolt" msgstr "" #. module: stock_available_mrp @@ -35,23 +35,37 @@ msgstr "" msgid "Product" msgstr "Produto" -#. module: stock_available_mrp -#: model:ir.model,name:stock_available_mrp.model_product_template -msgid "Product Template" -msgstr "Modelo Produto" - #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build Kit" +msgid "Table Kit" msgstr "" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build kit." +msgid "Table kit" msgstr "" +#. module: stock_available_mrp +#: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "Unit(s)" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,weight_uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "kg" +msgstr "" + +#~ msgid "Product Template" +#~ msgstr "Modelo Produto" + #~ msgid "Potential" #~ msgstr "Potencial" diff --git a/stock_available_mrp/i18n/sl.po b/stock_available_mrp/i18n/sl.po index 096be0bde..9f62571ce 100644 --- a/stock_available_mrp/i18n/sl.po +++ b/stock_available_mrp/i18n/sl.po @@ -21,14 +21,14 @@ msgstr "" "%100==4 ? 2 : 3);\n" #. module: stock_available_mrp -#: model:product.product,name:stock_available_mrp.product_product_9_white -#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template -msgid "Apple Wireless Keyboard" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id +msgid "BOM" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id -msgid "Bill of Materials" +#: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template +msgid "Bolt" msgstr "" #. module: stock_available_mrp @@ -36,22 +36,36 @@ msgstr "" msgid "Product" msgstr "Proizvod" -#. module: stock_available_mrp -#: model:ir.model,name:stock_available_mrp.model_product_template -msgid "Product Template" -msgstr "Predloga proizvoda" - #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build Kit" +msgid "Table Kit" msgstr "" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build kit." +msgid "Table kit" msgstr "" +#. module: stock_available_mrp +#: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "Unit(s)" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,weight_uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "kg" +msgstr "" + +#~ msgid "Product Template" +#~ msgstr "Predloga proizvoda" + #~ msgid "Thousand" #~ msgstr "Tisoč" diff --git a/stock_available_mrp/i18n/zh_CN.po b/stock_available_mrp/i18n/zh_CN.po index 044142b9d..7d9fc6345 100644 --- a/stock_available_mrp/i18n/zh_CN.po +++ b/stock_available_mrp/i18n/zh_CN.po @@ -20,14 +20,14 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" #. module: stock_available_mrp -#: model:product.product,name:stock_available_mrp.product_product_9_white -#: model:product.template,name:stock_available_mrp.product_product_9_white_product_template -msgid "Apple Wireless Keyboard" +#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id +msgid "BOM" msgstr "" #. module: stock_available_mrp -#: model:ir.model.fields,field_description:stock_available_mrp.field_product_product_bom_id -msgid "Bill of Materials" +#: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template +msgid "Bolt" msgstr "" #. module: stock_available_mrp @@ -35,23 +35,37 @@ msgstr "" msgid "Product" msgstr "产品" -#. module: stock_available_mrp -#: model:ir.model,name:stock_available_mrp.model_product_template -msgid "Product Template" -msgstr "产品模板" - #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build Kit" +msgid "Table Kit" msgstr "" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template -msgid "Self Build kit." +msgid "Table kit" msgstr "" +#. module: stock_available_mrp +#: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "Unit(s)" +msgstr "" + +#. module: stock_available_mrp +#: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white +#: model:product.product,weight_uom_name:stock_available_mrp.product_kit_1a +#: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template +#: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template +msgid "kg" +msgstr "" + +#~ msgid "Product Template" +#~ msgstr "产品模板" + #~ msgid "Potential" #~ msgstr "潜在" From 03abcc8c4801e31827a0db78c6df850307e932a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Thu, 26 Sep 2019 15:02:47 +0000 Subject: [PATCH 22/29] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (7 of 7 strings) Translation: stock-logistics-warehouse-12.0/stock-logistics-warehouse-12.0-stock_available_mrp Translate-URL: https://translation.odoo-community.org/projects/stock-logistics-warehouse-12-0/stock-logistics-warehouse-12-0-stock_available_mrp/zh_CN/ --- stock_available_mrp/i18n/zh_CN.po | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/stock_available_mrp/i18n/zh_CN.po b/stock_available_mrp/i18n/zh_CN.po index 7d9fc6345..07f53d170 100644 --- a/stock_available_mrp/i18n/zh_CN.po +++ b/stock_available_mrp/i18n/zh_CN.po @@ -9,26 +9,27 @@ msgstr "" "Project-Id-Version: stock-logistics-warehouse (9.0)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-08-28 10:00+0000\n" -"PO-Revision-Date: 2016-09-04 06:06+0000\n" -"Last-Translator: Jeffery Chenn \n" -"Language-Team: Chinese (China) (http://www.transifex.com/oca/OCA-stock-" -"logistics-warehouse-9-0/language/zh_CN/)\n" +"PO-Revision-Date: 2019-09-26 15:05+0000\n" +"Last-Translator: 黎伟杰 <674416404@qq.com>\n" +"Language-Team: Chinese (China) (http://www.transifex.com/oca/" +"OCA-stock-logistics-warehouse-9-0/language/zh_CN/)\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 3.8\n" #. module: stock_available_mrp #: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id msgid "BOM" -msgstr "" +msgstr "BOM" #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white #: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template msgid "Bolt" -msgstr "" +msgstr "螺栓" #. module: stock_available_mrp #: model:ir.model,name:stock_available_mrp.model_product_product @@ -39,13 +40,13 @@ msgstr "产品" #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template msgid "Table Kit" -msgstr "" +msgstr "桌台套件" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template msgid "Table kit" -msgstr "" +msgstr "桌台套件" #. module: stock_available_mrp #: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white @@ -53,7 +54,7 @@ msgstr "" #: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template #: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template msgid "Unit(s)" -msgstr "" +msgstr "件" #. module: stock_available_mrp #: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white @@ -61,7 +62,7 @@ msgstr "" #: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template #: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template msgid "kg" -msgstr "" +msgstr "公斤" #~ msgid "Product Template" #~ msgstr "产品模板" From 43f03ebc4f43395ecf5756f38c30f77c4820c64b Mon Sep 17 00:00:00 2001 From: Yann Papouin Date: Thu, 12 Nov 2020 12:42:54 +0000 Subject: [PATCH 23/29] Translated using Weblate (French) Currently translated at 100.0% (7 of 7 strings) Translation: stock-logistics-warehouse-12.0/stock-logistics-warehouse-12.0-stock_available_mrp Translate-URL: https://translation.odoo-community.org/projects/stock-logistics-warehouse-12-0/stock-logistics-warehouse-12-0-stock_available_mrp/fr/ --- stock_available_mrp/i18n/fr.po | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/stock_available_mrp/i18n/fr.po b/stock_available_mrp/i18n/fr.po index fdfffdc24..faf91b5ca 100644 --- a/stock_available_mrp/i18n/fr.po +++ b/stock_available_mrp/i18n/fr.po @@ -8,26 +8,27 @@ msgstr "" "Project-Id-Version: stock-logistics-warehouse (8.0)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-01-14 01:38+0000\n" -"PO-Revision-Date: 2016-01-13 16:35+0000\n" -"Last-Translator: <>\n" -"Language-Team: French (http://www.transifex.com/oca/OCA-stock-logistics-" -"warehouse-8-0/language/fr/)\n" +"PO-Revision-Date: 2020-11-12 12:44+0000\n" +"Last-Translator: Yann Papouin \n" +"Language-Team: French (http://www.transifex.com/oca/" +"OCA-stock-logistics-warehouse-8-0/language/fr/)\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 3.10\n" #. module: stock_available_mrp #: model:ir.model.fields,field_description:stock_available_mrp.field_product_product__bom_id msgid "BOM" -msgstr "" +msgstr "Nomenclature" #. module: stock_available_mrp #: model:product.product,name:stock_available_mrp.product_computer_desk_bolt_white #: model:product.template,name:stock_available_mrp.product_computer_desk_bolt_white_product_template msgid "Bolt" -msgstr "" +msgstr "Boulon" #. module: stock_available_mrp #: model:ir.model,name:stock_available_mrp.model_product_product @@ -38,13 +39,13 @@ msgstr "Article" #: model:product.product,name:stock_available_mrp.product_kit_1a #: model:product.template,name:stock_available_mrp.product_kit_1a_product_template msgid "Table Kit" -msgstr "" +msgstr "Table en kit" #. module: stock_available_mrp #: model:product.product,description:stock_available_mrp.product_kit_1a #: model:product.template,description:stock_available_mrp.product_kit_1a_product_template msgid "Table kit" -msgstr "" +msgstr "Table en kit" #. module: stock_available_mrp #: model:product.product,uom_name:stock_available_mrp.product_computer_desk_bolt_white @@ -52,7 +53,7 @@ msgstr "" #: model:product.template,uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template #: model:product.template,uom_name:stock_available_mrp.product_kit_1a_product_template msgid "Unit(s)" -msgstr "" +msgstr "Unité(s)" #. module: stock_available_mrp #: model:product.product,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white @@ -60,7 +61,7 @@ msgstr "" #: model:product.template,weight_uom_name:stock_available_mrp.product_computer_desk_bolt_white_product_template #: model:product.template,weight_uom_name:stock_available_mrp.product_kit_1a_product_template msgid "kg" -msgstr "" +msgstr "kg" #~ msgid "Product Template" #~ msgstr "Modèle de produit" From bccb73635eb937dabef230ca8c58d7b47199f0d4 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 25 Feb 2021 17:49:01 +0100 Subject: [PATCH 24/29] [FIX] stock_available_mrp: upstream compatibility After this change https://github.com/odoo/odoo/commit/3d34d58388ab362bf8e69cc2e75adbdfda3331b2 it isn't possible to have an storable kit product anymore. The main issue is that we have to drop some uses cases that this module considered for this kind of situations. --- .../tests/test_potential_qty.py | 76 +------------------ 1 file changed, 3 insertions(+), 73 deletions(-) diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index 363ed7a44..3eaefe30b 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -24,19 +24,16 @@ class TestPotentialQty(TransactionCase): # We need to compute parent_left and parent_right of the locations as # they are used to compute qty_available of the product. self.location._parent_store_compute() - self.setup_demo_data() - - def setup_demo_data(self): #  An interesting product (multi-line BoM, variants) self.tmpl = self.browse_ref( 'mrp.product_product_table_kit_product_template') #  First variant self.var1 = self.browse_ref('mrp.product_product_table_kit') - self.var1.type = 'product' + self.var1.type = 'consu' #  Second variant self.var2 = self.browse_ref( 'stock_available_mrp.product_kit_1a') - self.var2.type = 'product' + self.var2.type = 'consu' # Make bolt a stockable product to be able to change its stock # we need to unreserve the existing move before being able to do it. bolt = self.env.ref('mrp.product_product_computer_desk_bolt') @@ -297,7 +294,7 @@ class TestPotentialQty(TransactionCase): p2 = self.product_model.create({ 'name': 'Test sub product with BOM', - 'type': 'product', + 'type': 'consu', 'uom_id': self.env.ref('uom.product_uom_unit').id, }) @@ -369,73 +366,6 @@ class TestPotentialQty(TransactionCase): p1.refresh() self.assertEqual(2.0, p1.potential_qty) - def test_component_stock_choice(self): - # Test to change component stock for compute BOM stock - - # Get a demo product with outgoing move (qty: 3) - prod = self.browse_ref('product.product_product_16') - - # Set on hand qty - self.create_inventory(prod.id, 3) - - # Create a product with BOM - p1 = self.product_model.create({ - 'name': 'Test product with BOM', - }) - bom_p1 = self.bom_model.create({ - 'product_tmpl_id': p1.product_tmpl_id.id, - 'product_id': p1.id, - 'product_qty': 1, - }) - - # Need 1 prod for that - self.bom_line_model.create({ - 'bom_id': bom_p1.id, - 'product_id': prod.id, - 'product_qty': 1, - }) - - # Default component is qty_available - p1.refresh() - self.assertEqual(3.0, p1.potential_qty) - - # Change to immediately usable - self.config.set_param('stock_available_mrp_based_on', - 'immediately_usable_qty') - - p1.refresh() - self.assertEqual(0.0, p1.potential_qty) - - # If iMac has a Bom and can be manufactured - component = self.product_model.create({ - 'name': 'component', - 'type': 'product' - }) - self.create_inventory(component.id, 5) - - imac_bom = self.bom_model.create({ - 'product_tmpl_id': prod.product_tmpl_id.id, - 'product_id': prod.id, - 'product_qty': 1, - 'type': 'phantom', - }) - - # Need 1 component for prod - self.bom_line_model.create({ - 'bom_id': imac_bom.id, - 'product_id': component.id, - 'product_qty': 1, - }) - - p1.refresh() - self.assertEqual(5.0, p1.potential_qty) - - # Changing to virtual (same as immediately in current config) - self.config.set_param('stock_available_mrp_based_on', - 'virtual_available') - p1.refresh() - self.assertEqual(5.0, p1.potential_qty) - def test_potential_qty_list(self): # Try to highlight a bug when _get_potential_qty is called on # a recordset with multiple products From b5d85c22457b0de9172746355bfed3523b3ff2e2 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 26 Feb 2021 00:19:39 +0000 Subject: [PATCH 25/29] stock_available_mrp 12.0.1.0.1 --- stock_available_mrp/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_available_mrp/__manifest__.py b/stock_available_mrp/__manifest__.py index 2e3fe43f2..8261c5d2e 100644 --- a/stock_available_mrp/__manifest__.py +++ b/stock_available_mrp/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '12.0.1.0.0', + 'version': '12.0.1.0.1', "author": "Numérigraphe," "Odoo Community Association (OCA)", 'website': 'https://github.com/OCA/stock-logistics-warehouse', From 5d4a56bcb52f4c5cf70fbfc75a4dcc6000682310 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 16 Apr 2021 12:10:05 +0200 Subject: [PATCH 26/29] [FIX] stock_available_mrp: don't force type In Odoo change odoo/odoo@3d34d58 the products with Kit BoMs couldn't longer be other than `consu`. This was reverted in odoo/odoo@6c4623a and some products are raising errors when forcing the type --- stock_available_mrp/tests/test_potential_qty.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index 3eaefe30b..a2ede76c3 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -29,11 +29,9 @@ class TestPotentialQty(TransactionCase): 'mrp.product_product_table_kit_product_template') #  First variant self.var1 = self.browse_ref('mrp.product_product_table_kit') - self.var1.type = 'consu' #  Second variant self.var2 = self.browse_ref( 'stock_available_mrp.product_kit_1a') - self.var2.type = 'consu' # Make bolt a stockable product to be able to change its stock # we need to unreserve the existing move before being able to do it. bolt = self.env.ref('mrp.product_product_computer_desk_bolt') From fa1fef4694c2f26fe9bae95c3daf08c38aba7255 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 16 Apr 2021 11:58:06 +0000 Subject: [PATCH 27/29] stock_available_mrp 12.0.1.0.2 --- stock_available_mrp/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_available_mrp/__manifest__.py b/stock_available_mrp/__manifest__.py index 8261c5d2e..27725f4ad 100644 --- a/stock_available_mrp/__manifest__.py +++ b/stock_available_mrp/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Consider the production potential is available to promise', - 'version': '12.0.1.0.1', + 'version': '12.0.1.0.2', "author": "Numérigraphe," "Odoo Community Association (OCA)", 'website': 'https://github.com/OCA/stock-logistics-warehouse', From 84a04a66d02728f27902138f6775b3c3b3a501be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Thu, 13 May 2021 16:17:07 +0200 Subject: [PATCH 28/29] [IMP] stock_available_mrp: black, isort, prettier --- .../odoo/addons/stock_available_mrp | 1 + setup/stock_available_mrp/setup.py | 6 + stock_available_mrp/__manifest__.py | 24 +- stock_available_mrp/demo/mrp_data.xml | 81 +-- stock_available_mrp/models/product_product.py | 79 ++- .../tests/test_potential_qty.py | 491 ++++++++++-------- 6 files changed, 370 insertions(+), 312 deletions(-) create mode 120000 setup/stock_available_mrp/odoo/addons/stock_available_mrp create mode 100644 setup/stock_available_mrp/setup.py diff --git a/setup/stock_available_mrp/odoo/addons/stock_available_mrp b/setup/stock_available_mrp/odoo/addons/stock_available_mrp new file mode 120000 index 000000000..cd15a8cae --- /dev/null +++ b/setup/stock_available_mrp/odoo/addons/stock_available_mrp @@ -0,0 +1 @@ +../../../../stock_available_mrp \ No newline at end of file diff --git a/setup/stock_available_mrp/setup.py b/setup/stock_available_mrp/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/stock_available_mrp/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_available_mrp/__manifest__.py b/stock_available_mrp/__manifest__.py index 27725f4ad..eafbba1d1 100644 --- a/stock_available_mrp/__manifest__.py +++ b/stock_available_mrp/__manifest__.py @@ -1,19 +1,13 @@ # Copyright 2014 Numérigraphe SARL, Camptocamp # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { - 'name': 'Consider the production potential is available to promise', - 'version': '12.0.1.0.2', - "author": "Numérigraphe," - "Odoo Community Association (OCA)", - 'website': 'https://github.com/OCA/stock-logistics-warehouse', - 'category': 'Hidden', - 'depends': [ - 'stock_available', - 'mrp' - ], - 'demo': [ - 'demo/mrp_data.xml', - ], - 'license': 'AGPL-3', - 'installable': True, + "name": "Consider the production potential is available to promise", + "version": "12.0.1.0.2", + "author": "Numérigraphe," "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "category": "Hidden", + "depends": ["stock_available", "mrp"], + "demo": ["demo/mrp_data.xml",], + "license": "AGPL-3", + "installable": True, } diff --git a/stock_available_mrp/demo/mrp_data.xml b/stock_available_mrp/demo/mrp_data.xml index 1dc82fee9..ae3e3c4fc 100644 --- a/stock_available_mrp/demo/mrp_data.xml +++ b/stock_available_mrp/demo/mrp_data.xml @@ -1,38 +1,47 @@ - + - - PCSC234-WHITE - - - - - - - - - - - - - - Bolt - - 1.0 - 5.0 - product - - - BOLT-WHITE - - - - - 4 - - 5 - - - + + PCSC234-WHITE + + + + + + + + + + + Bolt + + 1.0 + 5.0 + product + + + BOLT-WHITE + + + + 4 + + 5 + + + diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index aa7111ca9..668112101 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -2,21 +2,18 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from collections import Counter + from odoo import api, fields, models from odoo.fields import first class ProductProduct(models.Model): - _inherit = 'product.product' + _inherit = "product.product" - bom_id = fields.Many2one( - 'mrp.bom', - compute='_compute_bom_id', - string='BOM' - ) + bom_id = fields.Many2one("mrp.bom", compute="_compute_bom_id", string="BOM") - @api.depends('virtual_available', 'bom_id', 'bom_id.product_qty') + @api.depends("virtual_available", "bom_id", "bom_id.product_qty") def _compute_available_quantities(self): super(ProductProduct, self)._compute_available_quantities() @@ -27,49 +24,46 @@ class ProductProduct(models.Model): :return: """ return [ - '|', - ('product_id', 'in', self.ids), - '&', - ('product_id', '=', False), - ('product_tmpl_id', 'in', self.mapped('product_tmpl_id.id')) + "|", + ("product_id", "in", self.ids), + "&", + ("product_id", "=", False), + ("product_tmpl_id", "in", self.mapped("product_tmpl_id.id")), ] @api.multi - @api.depends('product_tmpl_id') + @api.depends("product_tmpl_id") def _compute_bom_id(self): - bom_obj = self.env['mrp.bom'] - boms = bom_obj.search( - self._get_bom_id_domain(), - order='sequence, product_id', - ) + bom_obj = self.env["mrp.bom"] + boms = bom_obj.search(self._get_bom_id_domain(), order="sequence, product_id",) for product in self: product_boms = boms.filtered( - lambda b: b.product_id == product or - (not b.product_id and - b.product_tmpl_id == product.product_tmpl_id) + lambda b: b.product_id == product + or (not b.product_id and b.product_tmpl_id == product.product_tmpl_id) ) if product_boms: product.bom_id = first(product_boms) @api.multi def _compute_available_quantities_dict(self): - res, stock_dict = super(ProductProduct, - self)._compute_available_quantities_dict() + res, stock_dict = super( + ProductProduct, self + )._compute_available_quantities_dict() # compute qty for product with bom - product_with_bom = self.filtered('bom_id') + product_with_bom = self.filtered("bom_id") if not product_with_bom: return res, stock_dict - icp = self.env['ir.config_parameter'] + icp = self.env["ir.config_parameter"] stock_available_mrp_based_on = icp.sudo().get_param( - 'stock_available_mrp_based_on', 'qty_available' + "stock_available_mrp_based_on", "qty_available" ) # explode all boms at once exploded_boms = product_with_bom._explode_boms() # extract the list of product used as bom component - component_products = self.env['product.product'].browse() + component_products = self.env["product.product"].browse() for exploded_components in exploded_boms.values(): for bom_component in exploded_components: component_products |= first(bom_component).product_id @@ -79,22 +73,19 @@ class ProductProduct(models.Model): if res and stock_available_mrp_based_on in list(res.values())[0]: # If the qty is computed by the same method use it to avoid # stressing the cache - component_qties, _ = \ - component_products._compute_available_quantities_dict() + component_qties, _ = component_products._compute_available_quantities_dict() else: # The qty is a field computed by an other method than the # current one. Take the value on the record. component_qties = { - p.id: { - stock_available_mrp_based_on: p[ - stock_available_mrp_based_on]} for p in - component_products} + p.id: {stock_available_mrp_based_on: p[stock_available_mrp_based_on]} + for p in component_products + } for product in product_with_bom: # Need by product (same product can be in many BOM lines/levels) exploded_components = exploded_boms[product.id] - component_needs = product._get_components_needs( - exploded_components) + component_needs = product._get_components_needs(exploded_components) if not component_needs: # The BoM has no line we can use potential_qty = 0.0 @@ -102,13 +93,15 @@ class ProductProduct(models.Model): else: # Find the lowest quantity we can make with the stock at hand components_potential_qty = min( - [component_qties[component.id][ - stock_available_mrp_based_on] / need - for component, need in component_needs.items()] + [ + component_qties[component.id][stock_available_mrp_based_on] + / need + for component, need in component_needs.items() + ] ) bom_id = product.bom_id - potential_qty = (bom_id.product_qty * components_potential_qty) + potential_qty = bom_id.product_qty * components_potential_qty potential_qty = potential_qty > 0.0 and potential_qty or 0.0 # We want to respect the rounding factor of the potential_qty @@ -116,11 +109,11 @@ class ProductProduct(models.Model): potential_qty = bom_id.product_uom_id._compute_quantity( potential_qty, product.bom_id.product_tmpl_id.uom_id, - rounding_method='DOWN' + rounding_method="DOWN", ) - res[product.id]['potential_qty'] = potential_qty - res[product.id]['immediately_usable_qty'] += potential_qty + res[product.id]["potential_qty"] = potential_qty + res[product.id]["immediately_usable_qty"] += potential_qty return res, stock_dict @@ -145,6 +138,6 @@ class ProductProduct(models.Model): needs = Counter() for bom_component in exploded_components: component = bom_component[0].product_id - needs += Counter({component: bom_component[1]['qty']}) + needs += Counter({component: bom_component[1]["qty"]}) return needs diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index a2ede76c3..48f19e98b 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -1,8 +1,8 @@ # Copyright 2014 Numérigraphe SARL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo.tests.common import TransactionCase from odoo.osv.expression import TRUE_LEAF +from odoo.tests.common import TransactionCase class TestPotentialQty(TransactionCase): @@ -15,91 +15,90 @@ class TestPotentialQty(TransactionCase): self.bom_model = self.env["mrp.bom"] self.bom_line_model = self.env["mrp.bom.line"] self.stock_quant_model = self.env["stock.quant"] - self.config = self.env['ir.config_parameter'] - self.location = self.env['stock.location'] + self.config = self.env["ir.config_parameter"] + self.location = self.env["stock.location"] # Get the warehouses - self.wh_main = self.browse_ref('stock.warehouse0') - self.wh_ch = self.browse_ref('stock.stock_warehouse_shop0') + self.wh_main = self.browse_ref("stock.warehouse0") + self.wh_ch = self.browse_ref("stock.stock_warehouse_shop0") # We need to compute parent_left and parent_right of the locations as # they are used to compute qty_available of the product. self.location._parent_store_compute() #  An interesting product (multi-line BoM, variants) - self.tmpl = self.browse_ref( - 'mrp.product_product_table_kit_product_template') + self.tmpl = self.browse_ref("mrp.product_product_table_kit_product_template") #  First variant - self.var1 = self.browse_ref('mrp.product_product_table_kit') + self.var1 = self.browse_ref("mrp.product_product_table_kit") #  Second variant - self.var2 = self.browse_ref( - 'stock_available_mrp.product_kit_1a') + self.var2 = self.browse_ref("stock_available_mrp.product_kit_1a") # Make bolt a stockable product to be able to change its stock # we need to unreserve the existing move before being able to do it. - bolt = self.env.ref('mrp.product_product_computer_desk_bolt') - bolt_moves = self.env['stock.move'].search( - [('product_id', '=', bolt.id), - ('state', 'not in', ('done', 'cancel'))]) + bolt = self.env.ref("mrp.product_product_computer_desk_bolt") + bolt_moves = self.env["stock.move"].search( + [("product_id", "=", bolt.id), ("state", "not in", ("done", "cancel"))] + ) bolt_moves._do_unreserve() - bolt.type = 'product' + bolt.type = "product" # Components that can be used to make the product component_ids = [ # Bolt bolt.id, # Wood Panel - self.ref('mrp.product_product_wood_panel'), + self.ref("mrp.product_product_wood_panel"), ] # Zero-out the inventory of all variants and components - for component_id in ( - component_ids + [v.id - for v in self.tmpl.product_variant_ids]): + for component_id in component_ids + [ + v.id for v in self.tmpl.product_variant_ids + ]: prod = self.product_model.browse(component_id) - self.env['stock.quant'].search([ - ('product_id', '=', prod.id) - ]).unlink() + self.env["stock.quant"].search([("product_id", "=", prod.id)]).unlink() self.product_model.invalidate_cache() #  A product without a BoM - self.product_wo_bom = self.browse_ref('product.product_product_11') + self.product_wo_bom = self.browse_ref("product.product_product_11") # Record the initial quantity available for sale - self.initial_usable_qties = {i.id: i.immediately_usable_qty - for i in [self.tmpl, - self.var1, - self.var2, - self.product_wo_bom]} + self.initial_usable_qties = { + i.id: i.immediately_usable_qty + for i in [self.tmpl, self.var1, self.var2, self.product_wo_bom] + } def create_inventory(self, product_id, qty, location_id=None): if location_id is None: location_id = self.wh_main.lot_stock_id.id - inventory = self.env['stock.inventory'].create({ - 'name': 'Test inventory', - 'location_id': location_id, - 'filter': 'partial' - }) + inventory = self.env["stock.inventory"].create( + {"name": "Test inventory", "location_id": location_id, "filter": "partial"} + ) inventory.action_start() - self.env['stock.inventory.line'].create({ - 'inventory_id': inventory.id, - 'product_id': product_id, - 'location_id': location_id, - 'product_qty': qty - }) + self.env["stock.inventory.line"].create( + { + "inventory_id": inventory.id, + "product_id": product_id, + "location_id": location_id, + "product_qty": qty, + } + ) inventory._action_done() - def create_simple_bom(self, product, sub_product, - product_qty=1, sub_product_qty=1, - routing_id=False): - bom = self.bom_model.create({ - 'product_tmpl_id': product.product_tmpl_id.id, - 'product_id': product.id, - 'product_qty': product_qty, - 'routing_id': routing_id, - }) - self.bom_line_model.create({ - 'bom_id': bom.id, - 'product_id': sub_product.id, - 'product_qty': sub_product_qty, - }) + def create_simple_bom( + self, product, sub_product, product_qty=1, sub_product_qty=1, routing_id=False + ): + bom = self.bom_model.create( + { + "product_tmpl_id": product.product_tmpl_id.id, + "product_id": product.id, + "product_qty": product_qty, + "routing_id": routing_id, + } + ) + self.bom_line_model.create( + { + "bom_id": bom.id, + "product_id": sub_product.id, + "product_qty": sub_product_qty, + } + ) return bom @@ -109,235 +108,294 @@ class TestPotentialQty(TransactionCase): self.assertEqual(record.potential_qty, qty, msg) # Check the variation of quantity available for sale self.assertEqual( - (record.immediately_usable_qty - - self.initial_usable_qties[record.id]), qty, msg) + (record.immediately_usable_qty - self.initial_usable_qties[record.id]), + qty, + msg, + ) def test_potential_qty_no_bom(self): #  Check the potential when there's no BoM self.assertPotentialQty( - self.product_wo_bom, 0.0, - "The potential without a BoM should be 0") + self.product_wo_bom, 0.0, "The potential without a BoM should be 0" + ) def test_potential_qty_no_bom_for_company(self): - chicago_id = self.ref('stock.res_company_1') + chicago_id = self.ref("stock.res_company_1") # Receive 1000x Wood Panel owned by Chicago - inventory = self.env['stock.inventory'].create( - {'name': 'Receive CPUa8', - 'company_id': chicago_id, - 'location_id': self.wh_ch.lot_stock_id.id, - 'filter': 'partial'}) + inventory = self.env["stock.inventory"].create( + { + "name": "Receive CPUa8", + "company_id": chicago_id, + "location_id": self.wh_ch.lot_stock_id.id, + "filter": "partial", + } + ) inventory.action_start() - self.env['stock.inventory.line'].create( - {'inventory_id': inventory.id, - 'company_id': chicago_id, - 'product_id': self.ref('mrp.product_product_wood_panel'), - 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 1000.0}) + self.env["stock.inventory.line"].create( + { + "inventory_id": inventory.id, + "company_id": chicago_id, + "product_id": self.ref("mrp.product_product_wood_panel"), + "location_id": self.wh_ch.lot_stock_id.id, + "product_qty": 1000.0, + } + ) inventory._action_done() # Put Bolt owned by Chicago for 1000x the 1st variant in main WH - inventory = self.env['stock.inventory'].create( - {'name': 'components for 1st variant', - 'company_id': chicago_id, - 'location_id': self.wh_ch.lot_stock_id.id, - 'filter': 'partial'}) + inventory = self.env["stock.inventory"].create( + { + "name": "components for 1st variant", + "company_id": chicago_id, + "location_id": self.wh_ch.lot_stock_id.id, + "filter": "partial", + } + ) inventory.action_start() - self.env['stock.inventory.line'].create( - {'inventory_id': inventory.id, - 'company_id': chicago_id, - 'product_id': self.ref('mrp.product_product_computer_desk_bolt'), - 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 1000.0}) + self.env["stock.inventory.line"].create( + { + "inventory_id": inventory.id, + "company_id": chicago_id, + "product_id": self.ref("mrp.product_product_computer_desk_bolt"), + "location_id": self.wh_ch.lot_stock_id.id, + "product_qty": 1000.0, + } + ) inventory._action_done() self.assertPotentialQty( - self.tmpl, 250.0, - "Wrong template potential after receiving components") + self.tmpl, 250.0, "Wrong template potential after receiving components" + ) - test_user = self.env['res.users'].create( - {'name': 'test_demo', - 'login': 'test_demo', - 'company_id': self.ref('base.main_company'), - 'company_ids': [(4, self.ref('base.main_company'))], - 'groups_id': [(4, self.ref('stock.group_stock_user')), - (4, self.ref('mrp.group_mrp_user'))]}) + test_user = self.env["res.users"].create( + { + "name": "test_demo", + "login": "test_demo", + "company_id": self.ref("base.main_company"), + "company_ids": [(4, self.ref("base.main_company"))], + "groups_id": [ + (4, self.ref("stock.group_stock_user")), + (4, self.ref("mrp.group_mrp_user")), + ], + } + ) - bom = self.env['mrp.bom'].search( - [('product_tmpl_id', '=', self.tmpl.id)]) + bom = self.env["mrp.bom"].search([("product_tmpl_id", "=", self.tmpl.id)]) test_user_tmpl = self.tmpl.sudo(test_user) self.assertPotentialQty( - test_user_tmpl, 250.0, - "Simple user can access to the potential_qty") + test_user_tmpl, 250.0, "Simple user can access to the potential_qty" + ) # Set the bom on the main company (visible to members of main company) # and all products without company (visible to all) # and the demo user on Chicago (child of main company) - self.env['product.product'].search([ - TRUE_LEAF]).write({'company_id': False}) - test_user.write({'company_id': chicago_id, - 'company_ids': [(4, chicago_id)]}) - bom.company_id = self.ref('base.main_company') + self.env["product.product"].search([TRUE_LEAF]).write({"company_id": False}) + test_user.write({"company_id": chicago_id, "company_ids": [(4, chicago_id)]}) + bom.company_id = self.ref("base.main_company") self.assertPotentialQty( - test_user_tmpl, 0, + test_user_tmpl, + 0, "The bom should not be visible to non members of the bom's " - "company or company child of the bom's company") + "company or company child of the bom's company", + ) bom.company_id = chicago_id - self.assertPotentialQty( - test_user_tmpl, 250.0, '') + self.assertPotentialQty(test_user_tmpl, 250.0, "") def test_potential_qty(self): for i in [self.tmpl, self.var1, self.var2]: - self.assertPotentialQty( - i, 0.0, - "The potential quantity should start at 0") + self.assertPotentialQty(i, 0.0, "The potential quantity should start at 0") # Receive 1000x Wood Panel - inventory = self.env['stock.inventory'].create( - {'name': 'Receive Mouses', - 'location_id': self.wh_main.lot_stock_id.id, - 'filter': 'partial'}) + inventory = self.env["stock.inventory"].create( + { + "name": "Receive Mouses", + "location_id": self.wh_main.lot_stock_id.id, + "filter": "partial", + } + ) inventory.action_start() - self.env['stock.inventory.line'].create( - {'inventory_id': inventory.id, - 'product_id': self.ref('mrp.product_product_wood_panel'), - 'location_id': self.wh_main.lot_stock_id.id, - 'product_qty': 1000.0}) + self.env["stock.inventory.line"].create( + { + "inventory_id": inventory.id, + "product_id": self.ref("mrp.product_product_wood_panel"), + "location_id": self.wh_main.lot_stock_id.id, + "product_qty": 1000.0, + } + ) inventory._action_done() for i in [self.tmpl, self.var1, self.var2]: self.assertPotentialQty( - i, 0.0, + i, + 0.0, "Receiving a single component should not change the " - "potential of %s" % i) + "potential of %s" % i, + ) # Receive enough bolt to make 1000x the 1st variant in main WH - inventory = self.env['stock.inventory'].create( - {'name': 'components for 1st variant', - 'location_id': self.wh_main.lot_stock_id.id, - 'filter': 'partial'}) + inventory = self.env["stock.inventory"].create( + { + "name": "components for 1st variant", + "location_id": self.wh_main.lot_stock_id.id, + "filter": "partial", + } + ) inventory.action_start() - self.env['stock.inventory.line'].create( - {'inventory_id': inventory.id, - 'product_id': self.ref( - 'mrp.product_product_computer_desk_bolt'), - 'location_id': self.wh_main.lot_stock_id.id, - 'product_qty': 1000.0}) + self.env["stock.inventory.line"].create( + { + "inventory_id": inventory.id, + "product_id": self.ref("mrp.product_product_computer_desk_bolt"), + "location_id": self.wh_main.lot_stock_id.id, + "product_qty": 1000.0, + } + ) inventory._action_done() self.assertPotentialQty( - self.tmpl, 250.0, - "Wrong template potential after receiving components") + self.tmpl, 250.0, "Wrong template potential after receiving components" + ) self.assertPotentialQty( - self.var1, 250.0, - "Wrong variant 1 potential after receiving components") + self.var1, 250.0, "Wrong variant 1 potential after receiving components" + ) self.assertPotentialQty( - self.var2, 0.0, + self.var2, + 0.0, "Receiving variant 1's component should not change " - "variant 2's potential") + "variant 2's potential", + ) # Receive enough components to make 213 the 2nd variant at Chicago - inventory = self.env['stock.inventory'].create( - {'name': 'components for 2nd variant', - 'location_id': self.wh_ch.lot_stock_id.id, - 'filter': 'partial'}) + inventory = self.env["stock.inventory"].create( + { + "name": "components for 2nd variant", + "location_id": self.wh_ch.lot_stock_id.id, + "filter": "partial", + } + ) inventory.action_start() - self.env['stock.inventory.line'].create( - {'inventory_id': inventory.id, - 'product_id': self.ref('mrp.product_product_wood_panel'), - 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 1000.0}) - self.env['stock.inventory.line'].create( - {'inventory_id': inventory.id, - 'product_id': self.ref( - 'stock_available_mrp.product_computer_desk_bolt_white'), - 'location_id': self.wh_ch.lot_stock_id.id, - 'product_qty': 852.0}) + self.env["stock.inventory.line"].create( + { + "inventory_id": inventory.id, + "product_id": self.ref("mrp.product_product_wood_panel"), + "location_id": self.wh_ch.lot_stock_id.id, + "product_qty": 1000.0, + } + ) + self.env["stock.inventory.line"].create( + { + "inventory_id": inventory.id, + "product_id": self.ref( + "stock_available_mrp.product_computer_desk_bolt_white" + ), + "location_id": self.wh_ch.lot_stock_id.id, + "product_qty": 852.0, + } + ) inventory._action_done() self.assertPotentialQty( - self.tmpl.with_context(test=True), 250.0, - "Wrong template potential after receiving components") + self.tmpl.with_context(test=True), + 250.0, + "Wrong template potential after receiving components", + ) self.assertPotentialQty( - self.var1, 250.0, + self.var1, + 250.0, "Receiving variant 2's component should not change " - "variant 1's potential") + "variant 1's potential", + ) self.assertPotentialQty( - self.var2, 213.0, - "Wrong variant 2 potential after receiving components") + self.var2, 213.0, "Wrong variant 2 potential after receiving components" + ) # Check by warehouse self.assertPotentialQty( - self.tmpl.with_context(warehouse=self.wh_main.id), 250.0, - "Wrong potential quantity in main WH") + self.tmpl.with_context(warehouse=self.wh_main.id), + 250.0, + "Wrong potential quantity in main WH", + ) self.assertPotentialQty( - self.tmpl.with_context(warehouse=self.wh_ch.id), 213.0, - "Wrong potential quantity in Chicago WH") + self.tmpl.with_context(warehouse=self.wh_ch.id), + 213.0, + "Wrong potential quantity in Chicago WH", + ) # Check by location self.assertPotentialQty( - self.tmpl.with_context( - location=self.wh_main.lot_stock_id.id), 250.0, - "Wrong potential quantity in main WH location") + self.tmpl.with_context(location=self.wh_main.lot_stock_id.id), + 250.0, + "Wrong potential quantity in main WH location", + ) self.assertPotentialQty( - self.tmpl.with_context( - location=self.wh_ch.lot_stock_id.id), + self.tmpl.with_context(location=self.wh_ch.lot_stock_id.id), 213.0, - "Wrong potential quantity in Chicago WH location") + "Wrong potential quantity in Chicago WH location", + ) def test_multi_unit_recursive_bom(self): # Test multi-level and multi-units BOM - uom_unit = self.env.ref('uom.product_uom_unit') + uom_unit = self.env.ref("uom.product_uom_unit") uom_unit.rounding = 1.0 - p1 = self.product_model.create({ - 'name': 'Test product with BOM', - 'type': 'product', - 'uom_id': self.env.ref('uom.product_uom_unit').id, - }) + p1 = self.product_model.create( + { + "name": "Test product with BOM", + "type": "product", + "uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) - p2 = self.product_model.create({ - 'name': 'Test sub product with BOM', - 'type': 'consu', - 'uom_id': self.env.ref('uom.product_uom_unit').id, - }) + p2 = self.product_model.create( + { + "name": "Test sub product with BOM", + "type": "consu", + "uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) - p3 = self.product_model.create({ - 'name': 'Test component', - 'type': 'product', - 'uom_id': self.env.ref('uom.product_uom_unit').id, - }) + p3 = self.product_model.create( + { + "name": "Test component", + "type": "product", + "uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) - bom_p1 = self.bom_model.create({ - 'product_tmpl_id': p1.product_tmpl_id.id, - 'product_id': p1.id, - }) + bom_p1 = self.bom_model.create( + {"product_tmpl_id": p1.product_tmpl_id.id, "product_id": p1.id,} + ) - self.bom_line_model.create({ - 'bom_id': bom_p1.id, - 'product_id': p3.id, - 'product_qty': 1, - 'product_uom_id': self.env.ref('uom.product_uom_unit').id, - - }) + self.bom_line_model.create( + { + "bom_id": bom_p1.id, + "product_id": p3.id, + "product_qty": 1, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) # Two p2 which have a bom - self.bom_line_model.create({ - 'bom_id': bom_p1.id, - 'product_id': p2.id, - 'product_qty': 2, - 'product_uom_id': self.env.ref('uom.product_uom_unit').id, + self.bom_line_model.create( + { + "bom_id": bom_p1.id, + "product_id": p2.id, + "product_qty": 2, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) - }) - - bom_p2 = self.bom_model.create({ - 'product_tmpl_id': p2.product_tmpl_id.id, - 'product_id': p2.id, - 'type': 'phantom', - }) + bom_p2 = self.bom_model.create( + { + "product_tmpl_id": p2.product_tmpl_id.id, + "product_id": p2.id, + "type": "phantom", + } + ) # p2 need 2 unit of component - self.bom_line_model.create({ - 'bom_id': bom_p2.id, - 'product_id': p3.id, - 'product_qty': 2, - 'product_uom_id': self.env.ref('uom.product_uom_unit').id, - - }) + self.bom_line_model.create( + { + "bom_id": bom_p2.id, + "product_id": p3.id, + "product_qty": 2, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) p1.refresh() @@ -369,12 +427,11 @@ class TestPotentialQty(TransactionCase): # a recordset with multiple products # Recursive compute is not working - p1 = self.product_model.create({'name': 'Test P1'}) - p2 = self.product_model.create({'name': 'Test P2'}) - p3 = self.product_model.create({'name': 'Test P3', 'type': 'product'}) + p1 = self.product_model.create({"name": "Test P1"}) + p2 = self.product_model.create({"name": "Test P2"}) + p3 = self.product_model.create({"name": "Test P3", "type": "product"}) - self.config.set_param('stock_available_mrp_based_on', - 'immediately_usable_qty') + self.config.set_param("stock_available_mrp_based_on", "immediately_usable_qty") # P1 need one P2 self.create_simple_bom(p1, p2) @@ -385,11 +442,9 @@ class TestPotentialQty(TransactionCase): self.product_model.invalidate_cache() - products = self.product_model.search( - [('id', 'in', [p1.id, p2.id, p3.id])] - ) + products = self.product_model.search([("id", "in", [p1.id, p2.id, p3.id])]) self.assertEqual( {p1.id: 3.0, p2.id: 3.0, p3.id: 0.0}, - {p.id: p.potential_qty for p in products} + {p.id: p.potential_qty for p in products}, ) From 6ad6a36495c3963ad5dcfa2f49aec1e110cb4cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Thu, 13 May 2021 17:28:33 +0200 Subject: [PATCH 29/29] [MIG] stock_available_mrp: Migration to 13.0 --- stock_available_mrp/__manifest__.py | 4 +- stock_available_mrp/demo/mrp_data.xml | 8 +- stock_available_mrp/models/product_product.py | 24 +-- stock_available_mrp/readme/CONTRIBUTORS.rst | 4 + .../tests/test_potential_qty.py | 190 ++++++------------ 5 files changed, 87 insertions(+), 143 deletions(-) diff --git a/stock_available_mrp/__manifest__.py b/stock_available_mrp/__manifest__.py index eafbba1d1..114ddfa8e 100644 --- a/stock_available_mrp/__manifest__.py +++ b/stock_available_mrp/__manifest__.py @@ -2,12 +2,12 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Consider the production potential is available to promise", - "version": "12.0.1.0.2", + "version": "13.0.1.0.0", "author": "Numérigraphe," "Odoo Community Association (OCA)", "website": "https://github.com/OCA/stock-logistics-warehouse", "category": "Hidden", "depends": ["stock_available", "mrp"], - "demo": ["demo/mrp_data.xml",], + "demo": ["demo/mrp_data.xml"], "license": "AGPL-3", "installable": True, } diff --git a/stock_available_mrp/demo/mrp_data.xml b/stock_available_mrp/demo/mrp_data.xml index ae3e3c4fc..4e7b818ed 100644 --- a/stock_available_mrp/demo/mrp_data.xml +++ b/stock_available_mrp/demo/mrp_data.xml @@ -7,19 +7,19 @@ ref="mrp.product_product_table_kit_product_template" />
@@ -40,7 +40,7 @@ 5
diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index 668112101..fcd8af5c6 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -8,16 +8,16 @@ from odoo.fields import first class ProductProduct(models.Model): - _inherit = "product.product" - bom_id = fields.Many2one("mrp.bom", compute="_compute_bom_id", string="BOM") + bom_id = fields.Many2one( + comodel_name="mrp.bom", compute="_compute_bom_id", string="BOM" + ) @api.depends("virtual_available", "bom_id", "bom_id.product_qty") def _compute_available_quantities(self): - super(ProductProduct, self)._compute_available_quantities() + super()._compute_available_quantities() - @api.multi def _get_bom_id_domain(self): """ Real multi domain @@ -31,12 +31,12 @@ class ProductProduct(models.Model): ("product_tmpl_id", "in", self.mapped("product_tmpl_id.id")), ] - @api.multi @api.depends("product_tmpl_id") def _compute_bom_id(self): bom_obj = self.env["mrp.bom"] - boms = bom_obj.search(self._get_bom_id_domain(), order="sequence, product_id",) + boms = bom_obj.search(self._get_bom_id_domain(), order="sequence, product_id") for product in self: + product.bom_id = product.bom_id product_boms = boms.filtered( lambda b: b.product_id == product or (not b.product_id and b.product_tmpl_id == product.product_tmpl_id) @@ -44,11 +44,8 @@ class ProductProduct(models.Model): if product_boms: product.bom_id = first(product_boms) - @api.multi def _compute_available_quantities_dict(self): - res, stock_dict = super( - ProductProduct, self - )._compute_available_quantities_dict() + res, stock_dict = super()._compute_available_quantities_dict() # compute qty for product with bom product_with_bom = self.filtered("bom_id") @@ -88,8 +85,7 @@ class ProductProduct(models.Model): component_needs = product._get_components_needs(exploded_components) if not component_needs: # The BoM has no line we can use - potential_qty = 0.0 - + potential_qty = immediately_usable_qty = 0.0 else: # Find the lowest quantity we can make with the stock at hand components_potential_qty = min( @@ -113,11 +109,11 @@ class ProductProduct(models.Model): ) res[product.id]["potential_qty"] = potential_qty - res[product.id]["immediately_usable_qty"] += potential_qty + immediately_usable_qty = potential_qty if bom_id.type != "phantom" else 0 + res[product.id]["immediately_usable_qty"] += immediately_usable_qty return res, stock_dict - @api.multi def _explode_boms(self): """ return a dict by product_id of exploded bom lines diff --git a/stock_available_mrp/readme/CONTRIBUTORS.rst b/stock_available_mrp/readme/CONTRIBUTORS.rst index 4f314834d..1089a647b 100644 --- a/stock_available_mrp/readme/CONTRIBUTORS.rst +++ b/stock_available_mrp/readme/CONTRIBUTORS.rst @@ -4,3 +4,7 @@ * Laurent Mignon * Cédric Pigeon * Florian da Costa + +* `Tecnativa `_: + + * Víctor Martínez diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index 48f19e98b..022e0f58c 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -1,4 +1,5 @@ # Copyright 2014 Numérigraphe SARL +# Copyright 2021 Tecnativa - Víctor Martínez # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo.osv.expression import TRUE_LEAF @@ -9,7 +10,7 @@ class TestPotentialQty(TransactionCase): """Test the potential quantity on a product with a multi-line BoM""" def setUp(self): - super(TestPotentialQty, self).setUp() + super().setUp() self.product_model = self.env["product.product"] self.bom_model = self.env["mrp.bom"] @@ -17,13 +18,10 @@ class TestPotentialQty(TransactionCase): self.stock_quant_model = self.env["stock.quant"] self.config = self.env["ir.config_parameter"] self.location = self.env["stock.location"] + self.main_company = self.browse_ref("base.main_company") # Get the warehouses self.wh_main = self.browse_ref("stock.warehouse0") self.wh_ch = self.browse_ref("stock.stock_warehouse_shop0") - - # We need to compute parent_left and parent_right of the locations as - # they are used to compute qty_available of the product. - self.location._parent_store_compute() #  An interesting product (multi-line BoM, variants) self.tmpl = self.browse_ref("mrp.product_product_table_kit_product_template") #  First variant @@ -33,27 +31,20 @@ class TestPotentialQty(TransactionCase): # Make bolt a stockable product to be able to change its stock # we need to unreserve the existing move before being able to do it. bolt = self.env.ref("mrp.product_product_computer_desk_bolt") - bolt_moves = self.env["stock.move"].search( - [("product_id", "=", bolt.id), ("state", "not in", ("done", "cancel"))] - ) - bolt_moves._do_unreserve() + bolt.stock_move_ids._do_unreserve() bolt.type = "product" # Components that can be used to make the product - component_ids = [ + components = [ # Bolt - bolt.id, + bolt, # Wood Panel - self.ref("mrp.product_product_wood_panel"), + self.browse_ref("mrp.product_product_wood_panel"), ] # Zero-out the inventory of all variants and components - for component_id in component_ids + [ - v.id for v in self.tmpl.product_variant_ids - ]: - prod = self.product_model.browse(component_id) - self.env["stock.quant"].search([("product_id", "=", prod.id)]).unlink() + for component in components + [v for v in self.tmpl.product_variant_ids]: + component.stock_quant_ids.unlink() - self.product_model.invalidate_cache() #  A product without a BoM self.product_wo_bom = self.browse_ref("product.product_product_11") @@ -63,22 +54,37 @@ class TestPotentialQty(TransactionCase): for i in [self.tmpl, self.var1, self.var2, self.product_wo_bom] } - def create_inventory(self, product_id, qty, location_id=None): - if location_id is None: - location_id = self.wh_main.lot_stock_id.id - + def _create_inventory(self, location_id, company_id): inventory = self.env["stock.inventory"].create( - {"name": "Test inventory", "location_id": location_id, "filter": "partial"} + { + "name": "Test inventory", + "company_id": company_id, + "location_ids": [(4, location_id)], + "start_empty": True, + } ) inventory.action_start() + return inventory + + def _create_inventory_line(self, inventory_id, product_id, location_id, qty): self.env["stock.inventory.line"].create( { - "inventory_id": inventory.id, + "inventory_id": inventory_id, "product_id": product_id, "location_id": location_id, "product_qty": qty, } ) + + def create_inventory(self, product_id, qty, location_id=None, company_id=None): + if location_id is None: + location_id = self.wh_main.lot_stock_id.id + + if company_id is None: + company_id = self.main_company.id + + inventory = self._create_inventory(location_id, company_id) + self._create_inventory_line(inventory.id, product_id, location_id, qty) inventory._action_done() def create_simple_bom( @@ -121,48 +127,20 @@ class TestPotentialQty(TransactionCase): def test_potential_qty_no_bom_for_company(self): chicago_id = self.ref("stock.res_company_1") - # Receive 1000x Wood Panel owned by Chicago - inventory = self.env["stock.inventory"].create( - { - "name": "Receive CPUa8", - "company_id": chicago_id, - "location_id": self.wh_ch.lot_stock_id.id, - "filter": "partial", - } + self.create_inventory( + product_id=self.env.ref("mrp.product_product_wood_panel").id, + qty=1000.0, + location_id=self.wh_ch.lot_stock_id.id, + company_id=chicago_id, ) - inventory.action_start() - self.env["stock.inventory.line"].create( - { - "inventory_id": inventory.id, - "company_id": chicago_id, - "product_id": self.ref("mrp.product_product_wood_panel"), - "location_id": self.wh_ch.lot_stock_id.id, - "product_qty": 1000.0, - } - ) - inventory._action_done() - # Put Bolt owned by Chicago for 1000x the 1st variant in main WH - inventory = self.env["stock.inventory"].create( - { - "name": "components for 1st variant", - "company_id": chicago_id, - "location_id": self.wh_ch.lot_stock_id.id, - "filter": "partial", - } + self.create_inventory( + product_id=self.env.ref("mrp.product_product_computer_desk_bolt").id, + qty=1000.0, + location_id=self.wh_ch.lot_stock_id.id, + company_id=chicago_id, ) - inventory.action_start() - self.env["stock.inventory.line"].create( - { - "inventory_id": inventory.id, - "company_id": chicago_id, - "product_id": self.ref("mrp.product_product_computer_desk_bolt"), - "location_id": self.wh_ch.lot_stock_id.id, - "product_qty": 1000.0, - } - ) - inventory._action_done() self.assertPotentialQty( self.tmpl, 250.0, "Wrong template potential after receiving components" ) @@ -171,8 +149,8 @@ class TestPotentialQty(TransactionCase): { "name": "test_demo", "login": "test_demo", - "company_id": self.ref("base.main_company"), - "company_ids": [(4, self.ref("base.main_company"))], + "company_id": self.main_company.id, + "company_ids": [(4, self.main_company.id), (4, chicago_id)], "groups_id": [ (4, self.ref("stock.group_stock_user")), (4, self.ref("mrp.group_mrp_user")), @@ -182,7 +160,7 @@ class TestPotentialQty(TransactionCase): bom = self.env["mrp.bom"].search([("product_tmpl_id", "=", self.tmpl.id)]) - test_user_tmpl = self.tmpl.sudo(test_user) + test_user_tmpl = self.tmpl.with_user(test_user) self.assertPotentialQty( test_user_tmpl, 250.0, "Simple user can access to the potential_qty" ) @@ -191,8 +169,8 @@ class TestPotentialQty(TransactionCase): # and all products without company (visible to all) # and the demo user on Chicago (child of main company) self.env["product.product"].search([TRUE_LEAF]).write({"company_id": False}) - test_user.write({"company_id": chicago_id, "company_ids": [(4, chicago_id)]}) - bom.company_id = self.ref("base.main_company") + test_user.write({"company_ids": [(6, 0, self.main_company.ids)]}) + bom.company_id = self.main_company self.assertPotentialQty( test_user_tmpl, 0, @@ -200,6 +178,7 @@ class TestPotentialQty(TransactionCase): "company or company child of the bom's company", ) bom.company_id = chicago_id + test_user.write({"company_ids": [(4, chicago_id)]}) self.assertPotentialQty(test_user_tmpl, 250.0, "") def test_potential_qty(self): @@ -207,23 +186,11 @@ class TestPotentialQty(TransactionCase): self.assertPotentialQty(i, 0.0, "The potential quantity should start at 0") # Receive 1000x Wood Panel - inventory = self.env["stock.inventory"].create( - { - "name": "Receive Mouses", - "location_id": self.wh_main.lot_stock_id.id, - "filter": "partial", - } + self.create_inventory( + product_id=self.env.ref("mrp.product_product_wood_panel").id, + qty=1000.0, + location_id=self.wh_main.lot_stock_id.id, ) - inventory.action_start() - self.env["stock.inventory.line"].create( - { - "inventory_id": inventory.id, - "product_id": self.ref("mrp.product_product_wood_panel"), - "location_id": self.wh_main.lot_stock_id.id, - "product_qty": 1000.0, - } - ) - inventory._action_done() for i in [self.tmpl, self.var1, self.var2]: self.assertPotentialQty( i, @@ -233,23 +200,11 @@ class TestPotentialQty(TransactionCase): ) # Receive enough bolt to make 1000x the 1st variant in main WH - inventory = self.env["stock.inventory"].create( - { - "name": "components for 1st variant", - "location_id": self.wh_main.lot_stock_id.id, - "filter": "partial", - } + self.create_inventory( + product_id=self.env.ref("mrp.product_product_computer_desk_bolt").id, + qty=1000.0, + location_id=self.wh_main.lot_stock_id.id, ) - inventory.action_start() - self.env["stock.inventory.line"].create( - { - "inventory_id": inventory.id, - "product_id": self.ref("mrp.product_product_computer_desk_bolt"), - "location_id": self.wh_main.lot_stock_id.id, - "product_qty": 1000.0, - } - ) - inventory._action_done() self.assertPotentialQty( self.tmpl, 250.0, "Wrong template potential after receiving components" ) @@ -264,31 +219,20 @@ class TestPotentialQty(TransactionCase): ) # Receive enough components to make 213 the 2nd variant at Chicago - inventory = self.env["stock.inventory"].create( - { - "name": "components for 2nd variant", - "location_id": self.wh_ch.lot_stock_id.id, - "filter": "partial", - } + inventory = self._create_inventory( + self.wh_ch.lot_stock_id.id, self.ref("stock.res_company_1") ) - inventory.action_start() - self.env["stock.inventory.line"].create( - { - "inventory_id": inventory.id, - "product_id": self.ref("mrp.product_product_wood_panel"), - "location_id": self.wh_ch.lot_stock_id.id, - "product_qty": 1000.0, - } + self._create_inventory_line( + inventory.id, + self.ref("mrp.product_product_wood_panel"), + self.wh_ch.lot_stock_id.id, + 1000.0, ) - self.env["stock.inventory.line"].create( - { - "inventory_id": inventory.id, - "product_id": self.ref( - "stock_available_mrp.product_computer_desk_bolt_white" - ), - "location_id": self.wh_ch.lot_stock_id.id, - "product_qty": 852.0, - } + self._create_inventory_line( + inventory.id, + self.ref("stock_available_mrp.product_computer_desk_bolt_white"), + self.wh_ch.lot_stock_id.id, + 852.0, ) inventory._action_done() self.assertPotentialQty( @@ -357,7 +301,7 @@ class TestPotentialQty(TransactionCase): ) bom_p1 = self.bom_model.create( - {"product_tmpl_id": p1.product_tmpl_id.id, "product_id": p1.id,} + {"product_tmpl_id": p1.product_tmpl_id.id, "product_id": p1.id} ) self.bom_line_model.create(