mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[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:
committed by
Florian da Costa
parent
3ca3ea1061
commit
e870544677
@@ -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.
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user