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 @@
- -