diff --git a/stock_available_mrp/__manifest__.py b/stock_available_mrp/__manifest__.py
index 58a507f11..2e3fe43f2 100644
--- a/stock_available_mrp/__manifest__.py
+++ b/stock_available_mrp/__manifest__.py
@@ -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',
diff --git a/stock_available_mrp/demo/mrp_data.xml b/stock_available_mrp/demo/mrp_data.xml
index a41a04f4c..1dc82fee9 100644
--- a/stock_available_mrp/demo/mrp_data.xml
+++ b/stock_available_mrp/demo/mrp_data.xml
@@ -3,11 +3,11 @@
PCSC234-WHITE
+ ref="mrp.product_product_table_kit_product_template"/>
-
+
@@ -16,21 +16,21 @@
-
- Apple Wireless Keyboard
+
+ Bolt
- 10.0
- 47.0
+ 1.0
+ 5.0
product
-
-
- E-COM10-WHITE
+
+
+ BOLT-WHITE
-
- 1
-
+
+ 4
+
5
diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py
index 8798c3ffe..0cafb61e8 100644
--- a/stock_available_mrp/models/product_product.py
+++ b/stock_available_mrp/models/product_product.py
@@ -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')
diff --git a/stock_available_mrp/readme/CONTRIBUTORS.rst b/stock_available_mrp/readme/CONTRIBUTORS.rst
new file mode 100644
index 000000000..e274f35bc
--- /dev/null
+++ b/stock_available_mrp/readme/CONTRIBUTORS.rst
@@ -0,0 +1,5 @@
+* Loïc Bellier (Numérigraphe)
+* Lionel Sausin (Numérigraphe)
+* many thanks to Graeme Gellatly for his advice and code review
+* Laurent Mignon
+* Cédric Pigeon
diff --git a/stock_available_mrp/readme/DESCRIPTION.rst b/stock_available_mrp/readme/DESCRIPTION.rst
new file mode 100644
index 000000000..83df836a5
--- /dev/null
+++ b/stock_available_mrp/readme/DESCRIPTION.rst
@@ -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.
diff --git a/stock_available_mrp/readme/ROADMAP.rst b/stock_available_mrp/readme/ROADMAP.rst
new file mode 100644
index 000000000..7283e16c0
--- /dev/null
+++ b/stock_available_mrp/readme/ROADMAP.rst
@@ -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.
diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py
index cdd16916f..363ed7a44 100644
--- a/stock_available_mrp/tests/test_potential_qty.py
+++ b/stock_available_mrp/tests/test_potential_qty.py
@@ -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)