[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
This commit is contained in:
Lionel Sausin
2015-11-20 17:54:53 +01:00
committed by Víctor Martínez
parent 2dc71233be
commit e13f22c1ca
15 changed files with 389 additions and 278 deletions

View File

@@ -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
<https://github.com/OCA/stock-logistics-warehouse/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
<https://github.com/OCA/
stock-logistics-warehouse/issues/new?body=module:%20
stock_available%0Aversion:%20
8.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits
=======
@@ -33,16 +64,19 @@ Contributors
------------
* Loïc Bellier (Numérigraphe) <lb@numerigraphe.com>
* Lionel Sausin (Numérigraphe) <ls@numerigraphe.com>
* 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.

View File

@@ -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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# © 2014 Numérigraphe SARL
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import product
from . import models

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import orm, fields
import openerp.addons.decimal_precision as dp
class product_product(orm.Model):
"""Add the computation for the stock available to promise"""
_inherit = 'product.product'
def _product_available(self, cr, uid, ids, field_names=None, arg=False,
context=None):
"""Quantity available to promise based on components at hand."""
# Compute the core quantities
res = super(product_product, self)._product_available(
cr, uid, ids, field_names=field_names, arg=arg, context=context)
# If we didn't get a field_names list, there's nothing to do
if field_names is None:
return res
if context is None:
context = {}
# Prepare an alternative context without 'uom', to avoid cross-category
# conversions when reading the available stock of components
if 'uom' in context:
context_wo_uom = context.copy()
del context_wo_uom['uom']
else:
context_wo_uom = context
# Compute the production capacity
if any([f in field_names
for f in ['potential_qty', 'immediately_usable_qty']]):
# Compute the potential qty from BoMs with components available
bom_obj = self.pool['mrp.bom']
to_uom = 'uom' in context and self.pool['product.uom'].browse(
cr, SUPERUSER_ID, context['uom'], context=context)
for product in self.browse(cr, uid, ids, context=context):
# _bom_find() returns a single BoM id.
# We will not check any other BoM for this product
bom_id = bom_obj._bom_find(cr, SUPERUSER_ID, product.id,
product.uom_id.id)
if bom_id:
min_qty = self._compute_potential_qty_from_bom(
cr, uid, bom_id, to_uom or product.uom_id,
context=context)
if 'potential_qty' in field_names:
res[product.id]['potential_qty'] += min_qty
if 'immediately_usable_qty' in field_names:
res[product.id]['immediately_usable_qty'] += min_qty
return res
def _compute_potential_qty_from_bom(self, cr, uid, bom_id, to_uom,
context=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."),
}

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<!-- Add the quantity available to promise in the product form -->
<record id="view_product_form_potential_qty" model="ir.ui.view">
<field name="name">product.form.potential_qty</field>
<field name="model">product.product</field>
<field name="type">form</field>
<field name="inherit_id" ref="stock.view_normal_procurement_locations_form" />
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='virtual_available']" position="after">
<field name="potential_qty"/>
</xpath>
</data>
</field>
</record>
</data>
</openerp>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<!-- Add the quantity available to promise in the product form -->
<record id="view_product_form_potential_qty" model="ir.ui.view">
<field name="name">Potential quantity on product form</field>
<field name="model">product.template</field>
<field name="type">form</field>
<field name="inherit_id" ref="stock_available.view_stock_available_form" />
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='immediately_usable_qty']" position="before">
<field name="potential_qty"/>
</xpath>
</data>
</field>
</record>
</data>
</openerp>