diff options
| author | Simon Lejeune <sle@openerp.com> | 2019-09-19 13:04:48 +0000 |
|---|---|---|
| committer | Simon Lejeune <sle@openerp.com> | 2019-09-25 08:05:03 +0000 |
| commit | e75ea301f6f909ff918428ba2942bebe15d0db1b (patch) | |
| tree | a49192213871084286f386f1131111d05d6119ec | |
| parent | 8ea761824d3e492511ac13f718f009d6a2cc1270 (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.py | 14 | ||||
| -rw-r--r-- | addons/mrp/tests/test_workorder_operation.py | 252 |
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) |
