mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[FIX+IMP] stock_available_mrp: fix recursive potential_qty in list view + small improvements
This commit is contained in:
committed by
Víctor Martínez
parent
fb41ccd372
commit
4d2b9567e7
@@ -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',
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user