[10.0]stock_available: improve computation performance

[CHG] improve code regarding code review

[ADD] add test

[CHG] optimize stock computation by avoiding to call useless compute
This commit is contained in:
Cédric Pigeon
2017-05-09 12:35:23 +02:00
committed by Florian da Costa
parent 3ca3ea1061
commit e870544677
4 changed files with 147 additions and 52 deletions

View File

@@ -58,6 +58,7 @@ Contributors
* Lionel Sausin (Numérigraphe) <ls@numerigraphe.com>
* Sodexis <sodexis@sodexis.com>
* Cédric Pigeon <cedric.pigeon@acsone.eu>
Maintainer
----------
@@ -72,4 +73,4 @@ 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 https://odoo-community.org.
To contribute to this module, please visit https://odoo-community.org.

View File

@@ -16,39 +16,35 @@ class ProductProduct(models.Model):
_inherit = 'product.product'
@api.multi
@api.depends('virtual_available')
def _compute_immediately_usable_qty(self):
"""No-op implementation of the stock available to promise.
By default, available to promise = forecasted quantity.
**Each** sub-module **must** override this method in **both**
`product.product` **and** `product.template`, because we can't
decide in advance how to compute the template's quantity from the
variants.
"""
for prod in self:
prod.immediately_usable_qty = prod.virtual_available
def _compute_available_quantities_dict(self):
res = {}
for product in self:
res[product.id] = {}
res[product.id]['immediately_usable_qty'] = \
product.virtual_available
res[product.id]['potential_qty'] = 0.0
return res
@api.multi
@api.depends()
def _compute_potential_qty(self):
"""Set potential qty to 0.0 to define the field defintion used by
other modules to inherit it
"""
@api.depends('virtual_available')
def _compute_available_quantities(self):
res = self._compute_available_quantities_dict()
for product in self:
product.potential_qty = 0.0
data = res[product.id]
for key, value in data.iteritems():
if hasattr(product, key):
product[key] = value
immediately_usable_qty = fields.Float(
digits=dp.get_precision('Product Unit of Measure'),
compute='_compute_immediately_usable_qty',
compute='_compute_available_quantities',
string='Available to promise',
help="Stock for this Product that can be safely proposed "
"for sale to Customers.\n"
"The definition of this value can be configured to suit "
"your needs")
potential_qty = fields.Float(
compute='_compute_potential_qty',
compute='_compute_available_quantities',
digits=dp.get_precision('Product Unit of Measure'),
string='Potential',
help="Quantity of this Product that could be produced using "

View File

@@ -11,46 +11,44 @@ class ProductTemplate(models.Model):
_inherit = 'product.template'
@api.multi
@api.depends('product_variant_ids.immediately_usable_qty')
def _compute_immediately_usable_qty(self):
"""No-op implementation of the stock available to promise.
By default, available to promise = forecasted quantity.
**Each** sub-module **must** override this method in **both**
`product.product` **and** `product.template`, because we can't
decide in advance how to compute the template's quantity from the
variants.
"""
for tmpl in self:
tmpl.immediately_usable_qty = tmpl.virtual_available
@api.depends('product_variant_ids.immediately_usable_qty',
'product_variant_ids.potential_qty')
def _compute_available_quantities(self):
res = self._compute_available_quantities_dict()
for product in self:
data = res[product.id]
for key, value in data.iteritems():
if key in product._fields:
product[key] = value
@api.multi
@api.depends('product_variant_ids.potential_qty')
def _compute_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):
variants_dict = self.mapped(
'product_variant_ids')._compute_available_quantities_dict()
res = {}
for template in self:
immediately_usable_qty = sum(
[variants_dict[p.id]["immediately_usable_qty"] for p in
template.product_variant_ids])
potential_qty = max(
[variants_dict[p.id]["potential_qty"] for p in
template.product_variant_ids] or [0.0])
res[template.id] = {
"immediately_usable_qty": immediately_usable_qty,
"potential_qty": potential_qty,
}
return res
immediately_usable_qty = fields.Float(
digits=dp.get_precision('Product Unit of Measure'),
compute='_compute_immediately_usable_qty',
compute='_compute_available_quantities',
string='Available to promise',
help="Stock for this Product that can be safely proposed "
"for sale to Customers.\n"
"The definition of this value can be configured to suit "
"your needs")
potential_qty = fields.Float(
compute='_compute_potential_qty',
compute='_compute_available_quantities',
digits=dp.get_precision('Product Unit of Measure'),
string='Potential',
help="Quantity of this Product that could be produced using "

View File

@@ -7,7 +7,6 @@ from odoo.tests.common import TransactionCase
class TestStockLogisticsWarehouse(TransactionCase):
def test_res_config(self):
"""Test the config file"""
stock_setting = self.env['stock.config.settings'].create({})
@@ -20,3 +19,104 @@ class TestStockLogisticsWarehouse(TransactionCase):
self.assertEquals(
stock_setting.stock_available_mrp_based_on,
'immediately_usable_qty')
def test01_stock_levels(self):
"""checking that immediately_usable_qty actually reflects \
the variations in stock, both on product and template"""
moveObj = self.env['stock.move']
productObj = self.env['product.product']
templateObj = self.env['product.template']
supplier_location = self.env.ref('stock.stock_location_suppliers')
stock_location = self.env.ref('stock.stock_location_stock')
customer_location = self.env.ref('stock.stock_location_customers')
uom_unit = self.env.ref('product.product_uom_unit')
# Create product template
templateAB = templateObj.create(
{'name': 'templAB',
'uom_id': uom_unit.id,
})
# Create product A and B
productA = productObj.create(
{'name': 'product A',
'standard_price': 1,
'type': 'product',
'uom_id': uom_unit.id,
'default_code': 'A',
'product_tmpl_id': templateAB.id,
})
productB = productObj.create(
{'name': 'product B',
'standard_price': 1,
'type': 'product',
'uom_id': uom_unit.id,
'default_code': 'B',
'product_tmpl_id': templateAB.id,
})
# Create a stock move from INCOMING to STOCK
stockMoveInA = moveObj.create(
{'location_id': supplier_location.id,
'location_dest_id': stock_location.id,
'name': 'MOVE INCOMING -> STOCK ',
'product_id': productA.id,
'product_uom': productA.uom_id.id,
'product_uom_qty': 2,
})
stockMoveInB = moveObj.create(
{'location_id': supplier_location.id,
'location_dest_id': stock_location.id,
'name': 'MOVE INCOMING -> STOCK ',
'product_id': productB.id,
'product_uom': productB.uom_id.id,
'product_uom_qty': 3,
})
def compare_product_usable_qty(product, value):
# Refresh, because the function field is not recalculated between
# transactions
product.refresh()
self.assertEqual(product.immediately_usable_qty, value)
compare_product_usable_qty(productA, 0)
compare_product_usable_qty(templateAB, 0)
stockMoveInA.action_confirm()
compare_product_usable_qty(productA, 2)
compare_product_usable_qty(templateAB, 2)
stockMoveInA.action_assign()
compare_product_usable_qty(productA, 2)
compare_product_usable_qty(templateAB, 2)
stockMoveInA.action_done()
compare_product_usable_qty(productA, 2)
compare_product_usable_qty(templateAB, 2)
# will directly trigger action_done on productB
stockMoveInB.action_done()
compare_product_usable_qty(productA, 2)
compare_product_usable_qty(productB, 3)
compare_product_usable_qty(templateAB, 5)
# Create a stock move from STOCK to CUSTOMER
stockMoveOutA = moveObj.create(
{'location_id': stock_location.id,
'location_dest_id': customer_location.id,
'name': ' STOCK --> CUSTOMER ',
'product_id': productA.id,
'product_uom': productA.uom_id.id,
'product_uom_qty': 1,
'state': 'confirmed',
})
stockMoveOutA.action_done()
compare_product_usable_qty(productA, 1)
compare_product_usable_qty(templateAB, 4)
# Potential Qty is set as 0.0 by default
self.assertEquals(templateAB.potential_qty, 0.0)
self.assertEquals(productA.potential_qty, 0.0)