diff --git a/addons/mrp/data/mrp_demo.xml b/addons/mrp/data/mrp_demo.xml
index c168e4902ea..b8c2014d6e1 100644
--- a/addons/mrp/data/mrp_demo.xml
+++ b/addons/mrp/data/mrp_demo.xml
@@ -304,6 +304,7 @@
3
+ flexible
diff --git a/addons/mrp/models/mrp_abstract_workorder.py b/addons/mrp/models/mrp_abstract_workorder.py
index 54b31101140..1a34ac13c68 100644
--- a/addons/mrp/models/mrp_abstract_workorder.py
+++ b/addons/mrp/models/mrp_abstract_workorder.py
@@ -19,6 +19,11 @@ class MrpAbstractWorkorder(models.AbstractModel):
product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure', required=True, readonly=True)
final_lot_id = fields.Many2one('stock.production.lot', string='Lot/Serial Number', domain="[('product_id', '=', product_id)]")
product_tracking = fields.Selection(related="product_id.tracking")
+ consumption = fields.Selection([
+ ('strict', 'Strict'),
+ ('flexible', 'Flexible')],
+ required=True,
+ )
@api.onchange('qty_producing')
def _onchange_qty_producing(self):
@@ -217,6 +222,8 @@ class MrpAbstractWorkorder(models.AbstractModel):
def _update_raw_moves(self):
""" Once the production is done, the lots written on workorder lines
are saved on stock move lines"""
+ # Before writting produce quantities, we ensure they respect the bom strictness
+ self._strict_consumption_check()
vals_list = []
workorder_lines_to_process = self.workorder_line_ids.filtered(lambda line: line.qty_done > 0)
for line in workorder_lines_to_process:
@@ -227,6 +234,16 @@ class MrpAbstractWorkorder(models.AbstractModel):
self.workorder_line_ids.unlink()
self.env['stock.move.line'].create(vals_list)
+ def _strict_consumption_check(self):
+ if self.consumption == 'strict':
+ for move in self.move_raw_ids:
+ lines = self.workorder_line_ids.filtered(lambda l: l.move_id == move)
+ qty_done = sum(lines.mapped('qty_done'))
+ qty_to_consume = sum(lines.mapped('qty_to_consume'))
+ rounding = self.product_uom_id.rounding
+ if float_compare(qty_done, qty_to_consume, precision_rounding=rounding) != 0:
+ raise UserError(_('You should consume the quantity of %s defined in the BoM. If you want to consume more or less components, change the consumption setting on the BoM.') % lines[0].product_id.name)
+
class MrpAbstractWorkorderLine(models.AbstractModel):
_name = "mrp.abstract.workorder.line"
diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py
index 02418f2c588..284421cb283 100644
--- a/addons/mrp/models/mrp_bom.py
+++ b/addons/mrp/models/mrp_bom.py
@@ -61,6 +61,13 @@ class MrpBom(models.Model):
'res.company', 'Company',
default=lambda self: self.env['res.company']._company_default_get('mrp.bom'),
required=True)
+ consumption = fields.Selection([
+ ('strict', 'Strict'),
+ ('flexible', 'Flexible')],
+ help="Defines if you can consume more or less components than the quantity defined on the BoM.",
+ default='strict',
+ string='Consumption'
+ )
@api.onchange('product_id')
def onchange_product_id(self):
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 11c96450c2b..5c5dd19a6d2 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -177,10 +177,6 @@ class MrpProduction(models.Model):
post_visible = fields.Boolean(
'Allowed to Post Inventory', compute='_compute_post_visible',
help='Technical field to check when we can post')
- consumed_less_than_planned = fields.Boolean(
- compute='_compute_consumed_less_than_planned',
- help='Technical field used to see if we have to display a warning or not when confirming an order.')
-
user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self._uid)
company_id = fields.Many2one(
'res.company', 'Company',
@@ -340,19 +336,6 @@ class MrpProduction(models.Model):
else:
order.post_visible = order.is_locked and any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_finished_ids)
- @api.multi
- @api.depends('move_raw_ids.quantity_done', 'move_raw_ids.product_uom_qty')
- def _compute_consumed_less_than_planned(self):
- """ Display a warning to the user if a component of the BoM has less
- quantity than planned.
- """
- for order in self:
- order.consumed_less_than_planned = any(order.move_raw_ids.filtered(
- lambda move: float_compare(move.quantity_done,
- move.product_uom_qty,
- precision_rounding=move.product_uom.rounding) == -1)
- )
-
@api.multi
@api.depends('workorder_ids.state', 'move_finished_ids', 'move_finished_ids.quantity_done', 'is_locked')
def _get_produced_qty(self):
@@ -740,6 +723,7 @@ class MrpProduction(models.Model):
'state': len(workorders) == 0 and 'ready' or 'pending',
'qty_producing': quantity,
'capacity': operation.workcenter_id.capacity,
+ 'consumption': self.bom_id.consumption,
})
if workorders:
workorders[-1].next_work_order_id = workorder.id
diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py
index 99827f021fe..6821c41b5ce 100644
--- a/addons/mrp/tests/test_order.py
+++ b/addons/mrp/tests/test_order.py
@@ -5,6 +5,7 @@ from odoo.tests import Form
from datetime import datetime, timedelta
from odoo.fields import Datetime as Dt
+from odoo.exceptions import UserError
from odoo.addons.mrp.tests.common import TestMrpCommon
@@ -504,6 +505,7 @@ class TestMrpOrder(TestMrpCommon):
mo, bom, p_final, p1, p2 = self.generate_mo(tracking_base_1='serial', qty_base_1=1, qty_final=2)
self.assertEqual(len(mo), 1, 'MO should have been created')
+ bom.consumption = 'flexible'
lot_p1_1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': p1.id,
@@ -549,6 +551,7 @@ class TestMrpOrder(TestMrpCommon):
mo, _, p_final, p1, p2 = self.generate_mo(tracking_base_1='lot', qty_base_1=10, qty_final=1)
self.assertEqual(len(mo), 1, 'MO should have been created')
+ mo.bom_id.consumption = 'flexible'
first_lot_for_p1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': p1.id,
@@ -701,6 +704,7 @@ class TestMrpOrder(TestMrpCommon):
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 4)
self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 1)
+ mo.bom_id.consumption = 'flexible'
mo.action_assign()
produce_form = Form(self.env['mrp.product.produce'].with_context({
@@ -723,6 +727,7 @@ class TestMrpOrder(TestMrpCommon):
# try adding another product that doesn't belong to the BoM
with produce_form.workorder_line_ids.new() as line:
line.product_id = self.product_4
+ line.qty_done = 1
produce_wizard = produce_form.save()
produce_wizard.do_produce()
@@ -841,6 +846,62 @@ class TestMrpOrder(TestMrpCommon):
self.assertEqual(self.env['stock.quant']._gather(p2, self.stock_location, strict=True).quantity, -1)
self.assertEqual(self.env['stock.quant']._gather(p_final, self.stock_location, strict=True).quantity, 3)
+ def test_product_produce_9(self):
+ """ Check that no produce line are created when the consumed products are not tracked """
+ self.stock_location = self.env.ref('stock.stock_location_stock')
+ mo, bom, p_final, p1, p2 = self.generate_mo()
+ self.assertEqual(len(mo), 1, 'MO should have been created')
+
+ self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100)
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5)
+
+ mo.action_assign()
+
+ produce_form = Form(self.env['mrp.product.produce'].with_context({
+ 'active_id': mo.id,
+ 'active_ids': [mo.id],
+ }))
+
+ with self.assertRaises(UserError):
+ # try adding another line for a bom product to increase the quantity
+ with produce_form.workorder_line_ids.new() as line:
+ line.product_id = p1
+ line.qty_done = 1
+ product_produce = produce_form.save()
+ product_produce.do_produce()
+
+ with self.assertRaises(UserError):
+ # Try updating qty_done
+ product_produce = produce_form.save()
+ product_produce.workorder_line_ids[0].qty_done += 1
+ product_produce.do_produce()
+
+ with self.assertRaises(UserError):
+ # try adding another product
+ produce_form = Form(self.env['mrp.product.produce'].with_context({
+ 'active_id': mo.id,
+ 'active_ids': [mo.id],
+ }))
+ with produce_form.workorder_line_ids.new() as line:
+ line.product_id = self.product_4
+ line.qty_done = 1
+ product_produce = produce_form.save()
+ product_produce.do_produce()
+
+ # try adding another line for a bom product but the total quantity is good
+ produce_form = Form(self.env['mrp.product.produce'].with_context({
+ 'active_id': mo.id,
+ 'active_ids': [mo.id],
+ }))
+ produce_form.qty_producing = 1
+
+ with produce_form.workorder_line_ids.new() as line:
+ line.product_id = p1
+ line.qty_done = 1
+ product_produce = produce_form.save()
+ product_produce.workorder_line_ids[1].qty_done -= 1
+ product_produce.do_produce()
+
def test_product_produce_uom(self):
plastic_laminate = self.env.ref('mrp.product_product_plastic_laminate')
bom = self.env.ref('mrp.mrp_bom_plastic_laminate')
diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py
index b182ff72c03..00bb48f31a0 100644
--- a/addons/mrp/tests/test_workorder_operation.py
+++ b/addons/mrp/tests/test_workorder_operation.py
@@ -25,6 +25,7 @@ class TestWorkOrderProcess(common.TransactionCase):
self.env['stock.move'].search([('product_id', 'in', [product_bolt.id, product_screw.id])])._do_unreserve()
(product_bolt + product_screw).write({'type': 'product'})
+ self.env.ref("mrp.mrp_bom_desk").consumption = 'flexible'
production_table_form = Form(self.env['mrp.production'])
production_table_form.product_id = dining_table
production_table_form.bom_id = self.env.ref("mrp.mrp_bom_desk")
@@ -128,6 +129,7 @@ class TestWorkOrderProcess(common.TransactionCase):
bom = self.env['mrp.bom'].browse(self.ref("mrp.mrp_bom_desk"))
bom.routing_id = self.ref('mrp.mrp_routing_1')
+ bom.consumption = 'flexible'
bom.bom_line_ids.filtered(lambda p: p.product_id == product_table_sheet).operation_id = bom.routing_id.operation_ids[0]
bom.bom_line_ids.filtered(lambda p: p.product_id == product_table_leg).operation_id = bom.routing_id.operation_ids[1]
diff --git a/addons/mrp/views/mrp_bom_views.xml b/addons/mrp/views/mrp_bom_views.xml
index 44f59931539..cbbd8e4c61a 100644
--- a/addons/mrp/views/mrp_bom_views.xml
+++ b/addons/mrp/views/mrp_bom_views.xml
@@ -73,6 +73,7 @@
+
diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml
index 80175d8c3f9..be2ead0ec44 100644
--- a/addons/mrp/views/mrp_production_views.xml
+++ b/addons/mrp/views/mrp_production_views.xml
@@ -35,9 +35,7 @@