[10.0][MIG] port stock_available_mrp to 10.0

This commit is contained in:
Cédric Pigeon
2017-05-09 21:26:20 +02:00
committed by Florian da Costa
parent e6ffa45529
commit e23de10ffd
8 changed files with 209 additions and 314 deletions

View File

@@ -69,6 +69,7 @@ Contributors
* Lionel Sausin (Numérigraphe) <ls@numerigraphe.com>
* many thanks to Graeme Gellatly for his advice and code review
* Laurent Mignon <laurent.mignon@acsone.eu>
* Cédric Pigeon <cedric.pigeon@acsone.eu>
Maintainer
----------

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="product_kit_1a" model="product.product">
<field name="default_code">PCSC234-WHITE</field>
<field name="product_tmpl_id"
ref="mrp.product_product_build_kit_product_template"/>
<field name="attribute_value_ids" eval="[(6,0,[ref('product.product_attribute_value_3')])]"/>
</record>
<record id="mrp.product_product_build_kit" model="product.product">
<field name="attribute_value_ids"
eval="[(6,0,[ref('product.product_attribute_value_4')])]"/>
</record>
<record id="mrp.mrp_bom_kit_line_2" model="mrp.bom.line">
<field name="attribute_value_ids" eval="[(6,0,[ref('product.product_attribute_value_4')])]"/>
</record>
<record id="product_product_9_white" model="product.product">
<field name="name">Apple Wireless Keyboard</field>
<field name="categ_id" ref="product.product_category_5"/>
<field name="standard_price">10.0</field>
<field name="list_price">47.0</field>
<field name="type">consu</field>
<field name="uom_id" ref="product.product_uom_unit"/>
<field name="uom_po_id" ref="product.product_uom_unit"/>
<field name="default_code">E-COM10-WHITE</field>
</record>
<record id="mrp_bom_kit_line_3" model="mrp.bom.line">
<field name="product_id" ref="product_product_9_white"/>
<field name="product_qty">1</field>
<field name="product_uom_id" ref="product.product_uom_unit"/>
<field name="sequence">5</field>
<field name="bom_id" ref="mrp.mrp_bom_kit"/>
<field name="attribute_value_ids" eval="[(6,0,[ref('product.product_attribute_value_3')])]"/>
</record>
</odoo>

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<odoo>
<!-- 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>
@@ -23,5 +22,4 @@
</data>
</field>
</record>
</data>
</openerp>
</odoo>