summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Lejeune <sle@openerp.com>2019-09-19 13:04:48 +0000
committerSimon Lejeune <sle@openerp.com>2019-09-25 08:05:03 +0000
commite75ea301f6f909ff918428ba2942bebe15d0db1b (patch)
treea49192213871084286f386f1131111d05d6119ec
parent8ea761824d3e492511ac13f718f009d6a2cc1270 (diff)
[FIX] mrp: workorder operations and kit
When a BoM was composed of a phantom BoM, there was mutliple bugs: - if not routing was set on the phantom BoM, the components were never consumed - if no operation was set on the BoM, its components were to be consumed on the last workorder of the BoM and of the phantom BoM. closes odoo/odoo#37304 Signed-off-by: Simon Lejeune (sle) <sle@openerp.com>
-rw-r--r--addons/mrp/models/mrp_production.py14
-rw-r--r--addons/mrp/tests/test_workorder_operation.py252
2 files changed, 262 insertions, 4 deletions
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 44b085dc5d8..a03841a3b1b 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -792,7 +792,6 @@ class MrpProduction(models.Model):
quantity = 1.0
for operation in bom.routing_id.operation_ids:
- # create workorder
workorder = workorders.create({
'name': operation.name,
'production_id': self.id,
@@ -808,12 +807,19 @@ class MrpProduction(models.Model):
workorders[-1]._start_nextworkorder()
workorders += workorder
- # assign moves; last operation receive all unassigned moves (which case ?)
- moves_raw = self.move_raw_ids.filtered(lambda move: move.operation_id == operation)
+ moves_raw = self.move_raw_ids.filtered(lambda move: move.operation_id == operation and move.bom_line_id.bom_id.routing_id == bom.routing_id)
moves_finished = self.move_finished_ids.filtered(lambda move: move.operation_id == operation)
+
+ # - Raw moves from a BoM where a routing was set but no operation was precised should
+ # be consumed at the last workorder of the linked routing.
+ # - Raw moves from a BoM where no rounting was set should be consumed at the last
+ # workorder of the main routing.
if len(workorders) == len(bom.routing_id.operation_ids):
- moves_raw |= self.move_raw_ids.filtered(lambda move: not move.operation_id)
+ moves_raw |= self.move_raw_ids.filtered(lambda move: not move.operation_id and move.bom_line_id.bom_id.routing_id == bom.routing_id)
+ moves_raw |= self.move_raw_ids.filtered(lambda move: not move.workorder_id and not move.bom_line_id.bom_id.routing_id)
+
moves_finished |= self.move_finished_ids.filtered(lambda move: move.product_id != self.product_id and not move.operation_id)
+
moves_raw.mapped('move_line_ids').write({'workorder_id': workorder.id})
(moves_finished | moves_raw).write({'workorder_id': workorder.id})
diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py
index 68635a5bb21..71022aaa9e8 100644
--- a/addons/mrp/tests/test_workorder_operation.py
+++ b/addons/mrp/tests/test_workorder_operation.py
@@ -3,6 +3,7 @@
from datetime import datetime, timedelta
from odoo.tests import Form
+from odoo.tests.common import SavepointCase
from odoo.addons.mrp.tests.common import TestMrpCommon
from odoo.exceptions import ValidationError, UserError
@@ -1068,3 +1069,254 @@ class TestWorkOrderProcess(TestMrpCommon):
mo.button_plan()
self.assertAlmostEqual(mo.date_planned_start, planned_date, delta=timedelta(seconds=10))
self.assertAlmostEqual(mo.date_planned_start, mo.workorder_ids.date_planned_start, delta=timedelta(seconds=10))
+
+
+class TestRoutingAndKits(SavepointCase):
+ @classmethod
+ def setUpClass(cls):
+ """
+ kit1 (consu)
+ compkit1
+ finished1
+ compfinished1
+
+ Finished1 (Bom1)
+ - compfinished1
+ - kit1
+ Kit1 (BomKit1)
+ - compkit1
+
+ Rounting1 (finished1)
+ - operation 1
+ - operation 2
+ Rounting2 (kit1)
+ - operation 1
+ """
+ super(TestRoutingAndKits, cls).setUpClass()
+ cls.uom_unit = cls.env['uom.uom'].search([
+ ('category_id', '=', cls.env.ref('uom.product_uom_categ_unit').id),
+ ('uom_type', '=', 'reference')
+ ], limit=1)
+ cls.kit1 = cls.env['product.product'].create({
+ 'name': 'kit1',
+ 'type': 'consu',
+ })
+ cls.compkit1 = cls.env['product.product'].create({
+ 'name': 'compkit1',
+ 'type': 'product',
+ })
+ cls.finished1 = cls.env['product.product'].create({
+ 'name': 'finished1',
+ 'type': 'product',
+ })
+ cls.compfinished1 = cls.env['product.product'].create({
+ 'name': 'compfinished',
+ 'type': 'product',
+ })
+ cls.workcenter_finished1 = cls.env['mrp.workcenter'].create({
+ 'name': 'workcenter1',
+ })
+ cls.workcenter_kit1 = cls.env['mrp.workcenter'].create({
+ 'name': 'workcenter2',
+ })
+ cls.routing_finished1 = cls.env['mrp.routing'].create({
+ 'name': 'routing for finished1',
+ })
+ cls.operation_finished1 = cls.env['mrp.routing.workcenter'].create({
+ 'sequence': 1,
+ 'name': 'finished operation 1',
+ 'workcenter_id': cls.workcenter_finished1.id,
+ 'routing_id': cls.routing_finished1.id,
+ })
+ cls.operation_finished2 = cls.env['mrp.routing.workcenter'].create({
+ 'sequence': 1,
+ 'name': 'finished operation 2',
+ 'workcenter_id': cls.workcenter_finished1.id,
+ 'routing_id': cls.routing_finished1.id,
+ })
+ cls.routing_kit1 = cls.env['mrp.routing'].create({
+ 'name': 'routing for kit1',
+ })
+ cls.operation_kit1 = cls.env['mrp.routing.workcenter'].create({
+ 'name': 'Kit operation',
+ 'workcenter_id': cls.workcenter_kit1.id,
+ 'routing_id': cls.routing_kit1.id,
+ })
+ cls.bom_finished1 = cls.env['mrp.bom'].create({
+ 'product_id': cls.finished1.id,
+ 'product_tmpl_id': cls.finished1.product_tmpl_id.id,
+ 'product_uom_id': cls.uom_unit.id,
+ 'product_qty': 1,
+ 'type': 'normal',
+ 'routing_id': cls.routing_finished1.id,
+ 'bom_line_ids': [
+ (0, 0, {'product_id': cls.compfinished1.id, 'product_qty': 1}),
+ (0, 0, {'product_id': cls.kit1.id, 'product_qty': 1}),
+ ]})
+ cls.bom_kit1 = cls.env['mrp.bom'].create({
+ 'product_id': cls.kit1.id,
+ 'product_tmpl_id': cls.kit1.product_tmpl_id.id,
+ 'product_uom_id': cls.uom_unit.id,
+ 'product_qty': 1,
+ 'type': 'phantom',
+ 'routing_id': cls.routing_kit1.id,
+ 'bom_line_ids': [
+ (0, 0, {'product_id': cls.compkit1.id, 'product_qty': 1}),
+ ]})
+
+ def test_1(self):
+ """Operations are set on `self.bom_kit1` but none on `self.bom_finished1`."""
+ self.bom_kit1.bom_line_ids.operation_id = self.operation_kit1
+
+ mo_form = Form(self.env['mrp.production'])
+ mo_form.product_id = self.finished1
+ mo_form.bom_id = self.bom_finished1
+ mo_form.product_qty = 1.0
+ mo = mo_form.save()
+
+ mo.action_confirm()
+ mo.button_plan()
+
+ self.assertEqual(len(mo.workorder_ids), 3)
+ self.assertEqual(len(mo.workorder_ids[0].raw_workorder_line_ids), 0)
+ self.assertEqual(mo.workorder_ids[1].raw_workorder_line_ids.product_id, self.compfinished1)
+ self.assertEqual(mo.workorder_ids[2].raw_workorder_line_ids.product_id, self.compkit1)
+
+ def test_2(self):
+ """Operations are not set on `self.bom_kit1` and `self.bom_finished1`."""
+ mo_form = Form(self.env['mrp.production'])
+ mo_form.product_id = self.finished1
+ mo_form.bom_id = self.bom_finished1
+ mo_form.product_qty = 1.0
+ mo = mo_form.save()
+
+ mo.action_confirm()
+ mo.button_plan()
+
+ self.assertEqual(len(mo.workorder_ids), 3)
+ self.assertEqual(len(mo.workorder_ids[0].raw_workorder_line_ids), 0)
+ self.assertEqual(mo.workorder_ids[1].raw_workorder_line_ids.product_id, self.compfinished1)
+ self.assertEqual(mo.workorder_ids[2].raw_workorder_line_ids.product_id, self.compkit1)
+
+ def test_3(self):
+ """Operations are set both `self.bom_kit1` and `self.bom_finished1`."""
+ self.bom_kit1.bom_line_ids.operation_id = self.operation_kit1
+ self.bom_finished1.bom_line_ids[0].operation_id = self.operation_finished1
+ self.bom_finished1.bom_line_ids[1].operation_id = self.operation_finished2
+
+ mo_form = Form(self.env['mrp.production'])
+ mo_form.product_id = self.finished1
+ mo_form.bom_id = self.bom_finished1
+ mo_form.product_qty = 1.0
+ mo = mo_form.save()
+
+ mo.action_confirm()
+ mo.button_plan()
+
+ self.assertEqual(len(mo.workorder_ids), 3)
+ self.assertEqual(mo.workorder_ids[0].raw_workorder_line_ids.product_id, self.compfinished1)
+ self.assertFalse(mo.workorder_ids[1].raw_workorder_line_ids.product_id.id)
+ self.assertEqual(mo.workorder_ids[2].raw_workorder_line_ids.product_id, self.compkit1)
+
+ def test_4(self):
+ """Operations are set on `self.kit1`, none are set on `self.bom_finished1` and a kit
+ without routing was added to `self.bom_finished1`. We expect the component of the kit
+ without routing to be consumed at the last workorder of the main BoM.
+ """
+ kit2 = self.env['product.product'].create({
+ 'name': 'kit2',
+ 'type': 'consu',
+ })
+ compkit2 = self.env['product.product'].create({
+ 'name': 'compkit2',
+ 'type': 'product',
+ })
+ bom_kit2 = self.env['mrp.bom'].create({
+ 'product_id': kit2.id,
+ 'product_tmpl_id': kit2.product_tmpl_id.id,
+ 'product_uom_id': self.uom_unit.id,
+ 'product_qty': 1,
+ 'type': 'phantom',
+ 'bom_line_ids': [(0, 0, {'product_id': compkit2.id, 'product_qty': 1})]
+ })
+ self.bom_finished1.write({'bom_line_ids': [(0, 0, {'product_id': kit2.id, 'product_qty': 1})]})
+
+ mo_form = Form(self.env['mrp.production'])
+ mo_form.product_id = self.finished1
+ mo_form.bom_id = self.bom_finished1
+ mo_form.product_qty = 1.0
+ mo = mo_form.save()
+
+ mo.action_confirm()
+ mo.button_plan()
+
+ self.assertEqual(len(mo.workorder_ids), 3)
+
+ self.assertEqual(len(mo.workorder_ids[0].raw_workorder_line_ids), 0)
+ self.assertEqual(set(mo.workorder_ids[1].raw_workorder_line_ids.product_id.ids), set([self.compfinished1.id, compkit2.id]))
+ self.assertEqual(mo.workorder_ids[2].raw_workorder_line_ids.product_id, self.compkit1)
+
+ def test_5(self):
+ # Main bom: set the normal component to the first of the two operations of the routing.
+ bomline_compfinished = self.bom_finished1.bom_line_ids.filtered(lambda bl: bl.product_id == self.compfinished1)
+ bomline_compfinished.operation_id = self.operation_finished1
+
+ # Main bom: the kit do not have an operation set but there's one on its bom
+ bomline_kit1 = self.bom_finished1.bom_line_ids - bomline_compfinished
+ self.assertFalse(bomline_kit1.operation_id.id)
+ self.bom_kit1.bom_line_ids.operation_id = self.bom_kit1.routing_id.operation_ids
+
+ # Main bom: add a kit without routing
+ kit2 = self.env['product.product'].create({
+ 'name': 'kit2',
+ 'type': 'consu',
+ })
+ compkit2 = self.env['product.product'].create({
+ 'name': 'compkit2',
+ 'type': 'product',
+ })
+ bom_kit2 = self.env['mrp.bom'].create({
+ 'product_id': kit2.id,
+ 'product_tmpl_id': kit2.product_tmpl_id.id,
+ 'product_uom_id': self.uom_unit.id,
+ 'product_qty': 1,
+ 'type': 'phantom',
+ 'bom_line_ids': [(0, 0, {'product_id': compkit2.id, 'product_qty': 1})]
+ })
+ self.bom_finished1.write({'bom_line_ids': [(0, 0, {'product_id': kit2.id, 'product_qty': 1})]})
+
+ mo_form = Form(self.env['mrp.production'])
+ mo_form.product_id = self.finished1
+ mo_form.bom_id = self.bom_finished1
+ mo_form.product_qty = 1.0
+ mo = mo_form.save()
+
+ mo.action_confirm()
+ mo.button_plan()
+
+ self.assertEqual(len(mo.workorder_ids), 3)
+ self.assertEqual(mo.workorder_ids[0].raw_workorder_line_ids.product_id, self.compfinished1)
+ self.assertEqual(mo.workorder_ids[1].raw_workorder_line_ids.product_id, compkit2)
+ self.assertEqual(mo.workorder_ids[2].raw_workorder_line_ids.product_id, self.compkit1)
+
+ def test_6(self):
+ """ Use the same routing on `self.bom_fnished1` and `self.kit1`. The workorders should not
+ be duplicated.
+ """
+ self.bom_finished1.bom_line_ids[0].operation_id = self.operation_finished1
+ self.bom_finished1.bom_line_ids[1].operation_id = self.operation_finished2
+ self.bom_kit1.routing_id = self.routing_finished1
+ self.bom_kit1.bom_line_ids.operation_id = self.operation_finished1
+
+ mo_form = Form(self.env['mrp.production'])
+ mo_form.product_id = self.finished1
+ mo_form.bom_id = self.bom_finished1
+ mo_form.product_qty = 1.0
+ mo = mo_form.save()
+
+ mo.action_confirm()
+ mo.button_plan()
+
+ self.assertEqual(len(mo.workorder_ids), 2)
+ self.assertEqual(set(mo.workorder_ids[0].raw_workorder_line_ids.product_id.ids), set([self.compfinished1.id, self.compkit1.id]))
+ self.assertFalse(mo.workorder_ids[1].raw_workorder_line_ids.product_id.id)