Migrate stock_available_mrp to v12

This commit is contained in:
Florian da Costa
2019-07-06 17:28:05 +02:00
parent e6a9e9bf43
commit 7d864341dd
7 changed files with 101 additions and 58 deletions

View File

@@ -2,7 +2,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Consider the production potential is available to promise',
'version': '11.0.1.0.0',
'version': '12.0.1.0.0',
"author": "Numérigraphe,"
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/stock-logistics-warehouse',

View File

@@ -3,11 +3,11 @@
<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"/>
ref="mrp.product_product_table_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">
<record id="mrp.product_product_table_kit" model="product.product">
<field name="attribute_value_ids"
eval="[(6,0,[ref('product.product_attribute_value_4')])]"/>
</record>
@@ -16,21 +16,21 @@
<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>
<record id="product_computer_desk_bolt_white" model="product.product">
<field name="name">Bolt</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="standard_price">1.0</field>
<field name="list_price">5.0</field>
<field name="type">product</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>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="default_code">BOLT-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="product_id" ref="product_computer_desk_bolt_white"/>
<field name="product_qty">4</field>
<field name="product_uom_id" ref="uom.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')])]"/>

View File

@@ -13,7 +13,7 @@ class ProductProduct(models.Model):
bom_id = fields.Many2one(
'mrp.bom',
compute='_compute_bom_id',
string='Bill of Materials'
string='BOM'
)
@api.depends('virtual_available', 'bom_id', 'bom_id.product_qty')

View File

@@ -0,0 +1,5 @@
* 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
* Laurent Mignon <laurent.mignon@acsone.eu>
* Cédric Pigeon <cedric.pigeon@acsone.eu>

View File

@@ -0,0 +1,6 @@
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.
By configuration, the "Potential quantity" can be computed based on other product field.
For example, "Potential quantity" can be the quantity that can be manufactured
with the components available to promise.

View File

@@ -0,0 +1,24 @@
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.
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.

View File

@@ -29,20 +29,28 @@ class TestPotentialQty(TransactionCase):
def setup_demo_data(self):
#  An interesting product (multi-line BoM, variants)
self.tmpl = self.browse_ref(
'mrp.product_product_build_kit_product_template')
'mrp.product_product_table_kit_product_template')
#  First variant
self.var1 = self.browse_ref('mrp.product_product_build_kit')
self.var1 = self.browse_ref('mrp.product_product_table_kit')
self.var1.type = 'product'
#  Second variant
self.var2 = self.browse_ref(
'stock_available_mrp.product_kit_1a')
self.var2.type = 'product'
# Make bolt a stockable product to be able to change its stock
# we need to unreserve the existing move before being able to do it.
bolt = self.env.ref('mrp.product_product_computer_desk_bolt')
bolt_moves = self.env['stock.move'].search(
[('product_id', '=', bolt.id),
('state', 'not in', ('done', 'cancel'))])
bolt_moves._do_unreserve()
bolt.type = 'product'
# Components that can be used to make the product
component_ids = [
# KeyBoard
self.ref('product.product_product_9'),
# Mouse
self.ref('product.product_product_12'),
# Bolt
bolt.id,
# Wood Panel
self.ref('mrp.product_product_wood_panel'),
]
# Zero-out the inventory of all variants and components
@@ -81,7 +89,7 @@ class TestPotentialQty(TransactionCase):
'location_id': location_id,
'product_qty': qty
})
inventory.action_done()
inventory._action_done()
def create_simple_bom(self, product, sub_product,
product_qty=1, sub_product_qty=1,
@@ -118,7 +126,7 @@ class TestPotentialQty(TransactionCase):
def test_potential_qty_no_bom_for_company(self):
chicago_id = self.ref('stock.res_company_1')
# Receive 1000x CPUI5s owned by Chicago
# Receive 1000x Wood Panel owned by Chicago
inventory = self.env['stock.inventory'].create(
{'name': 'Receive CPUa8',
'company_id': chicago_id,
@@ -128,12 +136,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_9'),
'product_id': self.ref('mrp.product_product_wood_panel'),
'location_id': self.wh_ch.lot_stock_id.id,
'product_qty': 1000.0})
inventory.action_done()
inventory._action_done()
# Put RAM-SR5 owned by Chicago for 1000x the 1st variant in main WH
# Put Bolt 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,
@@ -143,12 +151,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_12'),
'product_id': self.ref('mrp.product_product_computer_desk_bolt'),
'location_id': self.wh_ch.lot_stock_id.id,
'product_qty': 1000.0})
inventory.action_done()
inventory._action_done()
self.assertPotentialQty(
self.tmpl, 1000.0,
self.tmpl, 250.0,
"Wrong template potential after receiving components")
test_user = self.env['res.users'].create(
@@ -164,7 +172,7 @@ class TestPotentialQty(TransactionCase):
test_user_tmpl = self.tmpl.sudo(test_user)
self.assertPotentialQty(
test_user_tmpl, 1000.0,
test_user_tmpl, 250.0,
"Simple user can access to the potential_qty")
# Set the bom on the main company (visible to members of main company)
@@ -181,7 +189,7 @@ class TestPotentialQty(TransactionCase):
"company or company child of the bom's company")
bom.company_id = chicago_id
self.assertPotentialQty(
test_user_tmpl, 1000.0, '')
test_user_tmpl, 250.0, '')
def test_potential_qty(self):
for i in [self.tmpl, self.var1, self.var2]:
@@ -189,7 +197,7 @@ class TestPotentialQty(TransactionCase):
i, 0.0,
"The potential quantity should start at 0")
# Receive 1000x Mouses
# Receive 1000x Wood Panel
inventory = self.env['stock.inventory'].create(
{'name': 'Receive Mouses',
'location_id': self.wh_main.lot_stock_id.id,
@@ -197,17 +205,17 @@ class TestPotentialQty(TransactionCase):
inventory.action_start()
self.env['stock.inventory.line'].create(
{'inventory_id': inventory.id,
'product_id': self.ref('product.product_product_12'),
'product_id': self.ref('mrp.product_product_wood_panel'),
'location_id': self.wh_main.lot_stock_id.id,
'product_qty': 1000.0})
inventory.action_done()
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 keyboard to make 1000x the 1st variant in main WH
# Receive enough bolt 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,
@@ -216,22 +224,22 @@ class TestPotentialQty(TransactionCase):
self.env['stock.inventory.line'].create(
{'inventory_id': inventory.id,
'product_id': self.ref(
'product.product_product_9'),
'mrp.product_product_computer_desk_bolt'),
'location_id': self.wh_main.lot_stock_id.id,
'product_qty': 1000.0})
inventory.action_done()
inventory._action_done()
self.assertPotentialQty(
self.tmpl, 1000.0,
self.tmpl, 250.0,
"Wrong template potential after receiving components")
self.assertPotentialQty(
self.var1, 1000.0,
self.var1, 250.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 313 the 2nd variant at Chicago
# Receive enough components to make 213 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,
@@ -239,64 +247,64 @@ class TestPotentialQty(TransactionCase):
inventory.action_start()
self.env['stock.inventory.line'].create(
{'inventory_id': inventory.id,
'product_id': self.ref('product.product_product_12'),
'product_id': self.ref('mrp.product_product_wood_panel'),
'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(
'stock_available_mrp.product_product_9_white'),
'stock_available_mrp.product_computer_desk_bolt_white'),
'location_id': self.wh_ch.lot_stock_id.id,
'product_qty': 313.0})
inventory.action_done()
'product_qty': 852.0})
inventory._action_done()
self.assertPotentialQty(
self.tmpl, 1000.0,
self.tmpl.with_context(test=True), 250.0,
"Wrong template potential after receiving components")
self.assertPotentialQty(
self.var1, 1000.0,
self.var1, 250.0,
"Receiving variant 2's component should not change "
"variant 1's potential")
self.assertPotentialQty(
self.var2, 313.0,
self.var2, 213.0,
"Wrong variant 2 potential after receiving components")
# Check by warehouse
self.assertPotentialQty(
self.tmpl.with_context(warehouse=self.wh_main.id), 1000.0,
self.tmpl.with_context(warehouse=self.wh_main.id), 250.0,
"Wrong potential quantity in main WH")
self.assertPotentialQty(
self.tmpl.with_context(warehouse=self.wh_ch.id), 313.0,
self.tmpl.with_context(warehouse=self.wh_ch.id), 213.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,
location=self.wh_main.lot_stock_id.id), 250.0,
"Wrong potential quantity in main WH location")
self.assertPotentialQty(
self.tmpl.with_context(
location=self.wh_ch.lot_stock_id.id),
313.0,
213.0,
"Wrong potential quantity in Chicago WH location")
def test_multi_unit_recursive_bom(self):
# Test multi-level and multi-units BOM
uom_unit = self.env.ref('product.product_uom_unit')
uom_unit = self.env.ref('uom.product_uom_unit')
uom_unit.rounding = 1.0
p1 = self.product_model.create({
'name': 'Test product with BOM',
'type': 'product',
'uom_id': self.env.ref('product.product_uom_unit').id,
'uom_id': self.env.ref('uom.product_uom_unit').id,
})
p2 = self.product_model.create({
'name': 'Test sub product with BOM',
'type': 'product',
'uom_id': self.env.ref('product.product_uom_unit').id,
'uom_id': self.env.ref('uom.product_uom_unit').id,
})
p3 = self.product_model.create({
'name': 'Test component',
'type': 'product',
'uom_id': self.env.ref('product.product_uom_unit').id,
'uom_id': self.env.ref('uom.product_uom_unit').id,
})
bom_p1 = self.bom_model.create({
@@ -308,7 +316,7 @@ class TestPotentialQty(TransactionCase):
'bom_id': bom_p1.id,
'product_id': p3.id,
'product_qty': 1,
'product_uom_id': self.env.ref('product.product_uom_unit').id,
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
})
@@ -317,7 +325,7 @@ class TestPotentialQty(TransactionCase):
'bom_id': bom_p1.id,
'product_id': p2.id,
'product_qty': 2,
'product_uom_id': self.env.ref('product.product_uom_unit').id,
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
})
@@ -332,7 +340,7 @@ class TestPotentialQty(TransactionCase):
'bom_id': bom_p2.id,
'product_id': p3.id,
'product_qty': 2,
'product_uom_id': self.env.ref('product.product_uom_unit').id,
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
})
@@ -365,7 +373,7 @@ class TestPotentialQty(TransactionCase):
# Test to change component stock for compute BOM stock
# Get a demo product with outgoing move (qty: 3)
prod = self.browse_ref('product.product_product_20')
prod = self.browse_ref('product.product_product_16')
# Set on hand qty
self.create_inventory(prod.id, 3)