# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import datetime, timedelta from odoo import Command from odoo.tests import Form from odoo.tests.common import TransactionCase from odoo.addons.mrp.tests.common import TestMrpCommon from odoo.exceptions import ValidationError, UserError from freezegun import freeze_time class TestWorkOrderProcessCommon(TestMrpCommon): @classmethod def setUpClass(cls): super(TestWorkOrderProcessCommon, cls).setUpClass() cls.source_location_id = cls.stock_location_14.id cls.warehouse = cls.env.ref('stock.warehouse0') # setting up alternative workcenters cls.wc_alt_1 = cls.env['mrp.workcenter'].create({ 'name': 'Nuclear Workcenter bis', 'default_capacity': 3, 'time_start': 9, 'time_stop': 5, 'time_efficiency': 80, }) cls.wc_alt_2 = cls.env['mrp.workcenter'].create({ 'name': 'Nuclear Workcenter ter', 'default_capacity': 1, 'time_start': 10, 'time_stop': 5, 'time_efficiency': 85, }) cls.product_4.uom_id = cls.uom_unit cls.planning_bom = cls.env['mrp.bom'].create({ 'product_id': cls.product_4.id, 'product_tmpl_id': cls.product_4.product_tmpl_id.id, 'product_uom_id': cls.uom_unit.id, 'product_qty': 4.0, 'consumption': 'flexible', 'operation_ids': [ (0, 0, {'name': 'Gift Wrap Maching', 'workcenter_id': cls.workcenter_1.id, 'time_cycle': 15, 'sequence': 1}), ], 'type': 'normal', 'bom_line_ids': [ (0, 0, {'product_id': cls.product_2.id, 'product_qty': 2}), (0, 0, {'product_id': cls.product_1.id, 'product_qty': 4}) ]}) cls.dining_table = cls.env['product.product'].create({ 'name': 'Table (MTO)', 'type': 'product', 'tracking': 'serial', }) cls.product_table_sheet = cls.env['product.product'].create({ 'name': 'Table Top', 'type': 'product', 'tracking': 'serial', }) cls.product_table_leg = cls.env['product.product'].create({ 'name': 'Table Leg', 'type': 'product', 'tracking': 'lot', }) cls.product_bolt = cls.env['product.product'].create({ 'name': 'Bolt', 'type': 'product', }) cls.product_screw = cls.env['product.product'].create({ 'name': 'Screw', 'type': 'product', }) cls.mrp_workcenter = cls.env['mrp.workcenter'].create({ 'name': 'Assembly Line 1', 'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id, }) cls.mrp_bom_desk = cls.env['mrp.bom'].create({ 'product_tmpl_id': cls.dining_table.product_tmpl_id.id, 'product_uom_id': cls.env.ref('uom.product_uom_unit').id, 'sequence': 3, 'ready_to_produce': 'asap', 'consumption': 'flexible', 'operation_ids': [ (0, 0, {'workcenter_id': cls.mrp_workcenter.id, 'name': 'Manual Assembly'}), ], }) cls.mrp_bom_desk.write({ 'bom_line_ids': [ (0, 0, { 'product_id': cls.product_table_sheet.id, 'product_qty': 1, 'product_uom_id': cls.env.ref('uom.product_uom_unit').id, 'sequence': 1, 'operation_id': cls.mrp_bom_desk.operation_ids.id}), (0, 0, { 'product_id': cls.product_table_leg.id, 'product_qty': 4, 'product_uom_id': cls.env.ref('uom.product_uom_unit').id, 'sequence': 2, 'operation_id': cls.mrp_bom_desk.operation_ids.id}), (0, 0, { 'product_id': cls.product_bolt.id, 'product_qty': 4, 'product_uom_id': cls.env.ref('uom.product_uom_unit').id, 'sequence': 3, 'operation_id': cls.mrp_bom_desk.operation_ids.id}), (0, 0, { 'product_id': cls.product_screw.id, 'product_qty': 10, 'product_uom_id': cls.env.ref('uom.product_uom_unit').id, 'sequence': 4, 'operation_id': cls.mrp_bom_desk.operation_ids.id}), ] }) cls.mrp_workcenter_1 = cls.env['mrp.workcenter'].create({ 'name': 'Drill Station 1', 'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id, }) cls.mrp_workcenter_3 = cls.env['mrp.workcenter'].create({ 'name': 'Assembly Line 1', 'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id, }) cls.bom_laptop = cls.env['mrp.bom'].create({ 'product_tmpl_id': cls.laptop.product_tmpl_id.id, 'product_qty': 1, 'product_uom_id': cls.uom_unit.id, 'consumption': 'flexible', 'bom_line_ids': [(0, 0, { 'product_id': cls.graphics_card.id, 'product_qty': 1, 'product_uom_id': cls.uom_unit.id, })], 'operation_ids': [ (0, 0, {'name': 'Cutting Machine', 'workcenter_id': cls.mrp_workcenter_1.id, 'time_cycle': 12, 'sequence': 1}), ], }) def test_cancel_mo_with_routing(self): """ Cancel a Manufacturing Order with routing (so generate a Work Order) and produce some quantities. When cancelled, the MO must be marked as done and the WO must be cancelled. """ # Create MO product_to_build = self.env['product.product'].create({ 'name': 'Young Tom', 'type': 'product', }) product_to_use_1 = self.env['product.product'].create({ 'name': 'Botox', 'type': 'product', }) product_to_use_2 = self.env['product.product'].create({ 'name': 'Old Tom', 'type': 'product', }) bom = self.env['mrp.bom'].create({ 'product_id': product_to_build.id, 'product_tmpl_id': product_to_build.product_tmpl_id.id, 'product_uom_id': self.uom_unit.id, 'product_qty': 1.0, 'consumption': 'strict', 'type': 'normal', 'bom_line_ids': [ (0, 0, {'product_id': product_to_use_2.id, 'product_qty': 4}), (0, 0, {'product_id': product_to_use_1.id, 'product_qty': 1}) ], 'operation_ids': [ (0, 0, {'name': 'Gift Wrap Maching', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 15, 'sequence': 1}), ] }) mo_form = Form(self.env['mrp.production']) mo_form.product_id = product_to_build mo_form.bom_id = bom mo_form.product_qty = 5.0 manufacturing_order = mo_form.save() manufacturing_order.action_confirm() manufacturing_order.button_plan() workorder = manufacturing_order.workorder_ids # Produce some quantity workorder.button_start() workorder.qty_producing = 2 workorder.record_production() # Post Inventory manufacturing_order._post_inventory() backorder = manufacturing_order.procurement_group_id.mrp_production_ids[-1] # Cancel it backorder.action_cancel() # Check MO is done, WO is cancelled and its SML are done or cancelled self.assertEqual(manufacturing_order.state, 'done', "MO should be in done state.") self.assertEqual(workorder.state, 'done', "WO should be one.") self.assertEqual(manufacturing_order.move_raw_ids[0].state, 'done', "Due to 'post_inventory', some move raw must stay in done state") self.assertEqual(manufacturing_order.move_raw_ids[1].state, 'done', "Due to 'post_inventory', some move raw must stay in done state") self.assertEqual(backorder.move_raw_ids[0].state, 'cancel', "The other move raw are cancelled like their MO.") self.assertEqual(backorder.move_raw_ids[1].state, 'cancel', "The other move raw are cancelled like their MO.") self.assertEqual(manufacturing_order.move_finished_ids[0].state, 'done', "Due to 'post_inventory', a move finished must stay in done state") self.assertEqual(backorder.move_finished_ids[0].state, 'cancel', "The other move finished is cancelled like its MO.") def test_putaway_after_manufacturing_1(self): """ This test checks a manufactured product without tracking will go to location defined in putaway strategy. """ self.stock_location = self.env.ref('stock.stock_location_stock') self.depot_location = self.env['stock.location'].create({ 'name': 'Depot', 'usage': 'internal', 'location_id': self.stock_location.id, }) self.env["stock.putaway.rule"].create({ "location_in_id": self.stock_location.id, "location_out_id": self.depot_location.id, 'category_id': self.env.ref('product.product_category_all').id, }) self.env['stock.quant']._update_available_quantity(self.graphics_card, self.stock_location, 20) form = Form(self.env['mrp.production']) form.product_id = self.laptop form.product_qty = 1 form.bom_id = self.bom_laptop mo_laptop = form.save() mo_laptop.action_confirm() # form = Form(mo_laptop) form.qty_producing = 2.0 mo_laptop = form.save() mo_laptop.action_assign() mo_laptop.button_plan() workorder = mo_laptop.workorder_ids[0] workorder.button_start() workorder.record_production() mo_laptop.move_raw_ids.quantity_done = 2.0 mo_laptop.button_mark_done() # We check if the laptop go in the depot and not in the stock move = mo_laptop.move_finished_ids location_dest = move.move_line_ids.location_dest_id self.assertEqual(location_dest.id, self.depot_location.id) self.assertNotEqual(location_dest.id, self.stock_location.id) def test_putaway_after_manufacturing_2(self): """ This test checks a tracked manufactured product will go to location defined in putaway strategy. """ self.stock_location = self.env.ref('stock.stock_location_stock') self.depot_location = self.env['stock.location'].create({ 'name': 'Depot', 'usage': 'internal', 'location_id': self.stock_location.id, }) self.env["stock.putaway.rule"].create({ "location_in_id": self.stock_location.id, "location_out_id": self.depot_location.id, 'category_id': self.env.ref('product.product_category_all').id, }) self.env['stock.quant']._update_available_quantity(self.graphics_card, self.stock_location, 20) self.laptop.tracking = 'serial' form = Form(self.env['mrp.production']) form.product_id = self.laptop form.product_qty = 1 form.bom_id = self.bom_laptop mo_laptop = form.save() mo_laptop.action_confirm() mo_laptop.action_assign() mo_laptop.button_plan() workorder = mo_laptop.workorder_ids[0] workorder.button_start() serial = self.env['stock.lot'].create({'product_id': self.laptop.id, 'company_id': self.env.company.id}) workorder.finished_lot_id = serial workorder.record_production() mo_laptop.button_mark_done() # We check if the laptop go in the depot and not in the stock move = mo_laptop.move_finished_ids location_dest = move.move_line_ids.location_dest_id self.assertEqual(location_dest.id, self.depot_location.id) self.assertNotEqual(location_dest.id, self.stock_location.id) def test_backorder_1(self): """Operations are set on `self.bom_kit1` but none on `self.bom_finished1`.""" # TODO: This test name + description don't match the test (there are no backorders # or kits). Test should either be rewritten or renamed if someone can figure out # what its intended purpose is. self.finished1 = self.env['product.product'].create({ 'name': 'finished1', 'type': 'product', }) self.compfinished1 = self.env['product.product'].create({ 'name': 'compfinished1', 'type': 'product', }) self.compfinished2 = self.env['product.product'].create({ 'name': 'compfinished2', 'type': 'product', }) self.workcenter1 = self.env['mrp.workcenter'].create({ 'name': 'workcenter1', }) self.workcenter2 = self.env['mrp.workcenter'].create({ 'name': 'workcenter2', }) self.bom_finished1 = self.env['mrp.bom'].create({ 'product_id': self.finished1.id, 'product_tmpl_id': self.finished1.product_tmpl_id.id, 'product_uom_id': self.uom_unit.id, 'product_qty': 1, 'consumption': 'flexible', 'type': 'normal', 'bom_line_ids': [ (0, 0, {'product_id': self.compfinished1.id, 'product_qty': 1}), (0, 0, {'product_id': self.compfinished2.id, 'product_qty': 1}), ], 'operation_ids': [ (0, 0, {'sequence': 1, 'name': 'finished operation 1', 'workcenter_id': self.workcenter1.id}), (0, 0, {'sequence': 2, 'name': 'finished operation 2', 'workcenter_id': self.workcenter2.id}), ], }) self.bom_finished1.bom_line_ids[0].operation_id = self.bom_finished1.operation_ids[0].id self.bom_finished1.bom_line_ids[1].operation_id = self.bom_finished1.operation_ids[1].id self.env['quality.point'].create({ 'product_ids': [(4, self.finished1.id)], 'picking_type_ids': [(4, self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1).id)], 'operation_id': self.bom_finished1.operation_ids[0].id, 'test_type_id': self.env.ref('mrp_workorder.test_type_register_consumed_materials').id, 'component_id': self.compfinished1.id, }) self.env['quality.point'].create({ 'product_ids': [(4, self.finished1.id)], 'picking_type_ids': [(4, self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1).id)], 'operation_id': self.bom_finished1.operation_ids[1].id, 'test_type_id': self.env.ref('mrp_workorder.test_type_register_consumed_materials').id, 'component_id': self.compfinished2.id, }) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.finished1 mo_form.bom_id = self.bom_finished1 mo_form.product_qty = 2.0 mo = mo_form.save() mo.action_confirm() mo.button_plan() self.assertEqual(len(mo.workorder_ids), 2) workorder1 = mo.workorder_ids[0] workorder2 = mo.workorder_ids[1] self.assertEqual(workorder1.check_ids.component_id, self.compfinished1) self.assertEqual(workorder1.qty_remaining, 2) self.assertEqual(workorder2.check_ids.component_id, self.compfinished2) self.assertEqual(workorder2.qty_remaining, 2) mo.workorder_ids.check_ids.quality_state = 'pass' mo.move_raw_ids.quantity_done = 2.0 mo.qty_producing = 2.0 mo.with_context(debug=True).button_mark_done() self.assertEqual(workorder1.state, 'done') self.assertEqual(workorder2.state, 'done') def test_backorder_2(self): """Test if all the quality checks are retained when a backorder is created from the tablet view""" finished_product = self.env['product.product'].create({ 'name': 'finished_product', 'type': 'product', 'tracking': 'serial', }) component = self.env['product.product'].create({ 'name': 'component', 'type': 'product', }) workcenter = self.env['mrp.workcenter'].create({ 'name': 'workcenter', }) bom = self.env['mrp.bom'].create({ 'product_id': finished_product.id, 'product_tmpl_id': finished_product.product_tmpl_id.id, 'product_uom_id': self.uom_unit.id, 'product_qty': 1, 'consumption': 'flexible', 'type': 'normal', 'bom_line_ids': [ (0, 0, {'product_id': component.id, 'product_qty': 1}), ], 'operation_ids': [ (0, 0, {'sequence': 1, 'name': 'finished operation 1', 'workcenter_id': workcenter.id}), ], }) bom.bom_line_ids[0].operation_id = bom.operation_ids[0].id self.env['quality.point'].create({ 'product_ids': [(4, finished_product.id)], 'picking_type_ids': [ (4, self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1).id)], 'operation_id': bom.operation_ids[0].id, 'test_type_id': self.env.ref('quality.test_type_instructions').id, 'note': 'Installing VIM (pcs xi ipzth adi du ixbt)', }) mo_form = Form(self.env['mrp.production']) mo_form.product_id = finished_product mo_form.bom_id = bom mo_form.product_qty = 2.0 mo = mo_form.save() mo.action_confirm() mo.button_plan() wo = mo.workorder_ids[0] wo.button_start() wo.action_generate_serial() wo.current_quality_check_id.action_next() wo.current_quality_check_id.action_next() result = wo.do_finish() wo_backorder = self.env['mrp.workorder'].browse(result['res_id']) self.assertEqual(len(wo_backorder.check_ids), len(wo.check_ids)) class TestWorkOrderProcess(TestWorkOrderProcessCommon): def full_availability(self): """set full availability for all calendars""" calendar = self.env['resource.calendar'].search([]) calendar.write({'attendance_ids': [(5, 0, 0)]}) calendar.write({'attendance_ids': [ (0, 0, {'name': 'Monday', 'dayofweek': '0', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}), (0, 0, {'name': 'Tuesday', 'dayofweek': '1', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}), (0, 0, {'name': 'Wednesday', 'dayofweek': '2', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}), (0, 0, {'name': 'Thursday', 'dayofweek': '3', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}), (0, 0, {'name': 'Friday', 'dayofweek': '4', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}), (0, 0, {'name': 'Saturday', 'dayofweek': '5', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}), (0, 0, {'name': 'Sunday', 'dayofweek': '6', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}), ]}) def test_00_workorder_process(self): """ Testing consume quants and produced quants with workorder """ dining_table = self.dining_table product_table_sheet = self.product_table_sheet product_table_leg = self.product_table_leg product_bolt = self.product_bolt product_screw = self.product_screw mrp_bom_desk = self.mrp_bom_desk mrp_bom_desk.bom_line_ids.operation_id = False mrp_bom_desk.bom_line_ids.manual_consumption = False self.env['stock.move'].search([('product_id', 'in', [product_bolt.id, product_screw.id])])._do_unreserve() # Set tracking lot on finish and consume products. dining_table.tracking = 'lot' product_table_sheet.tracking = 'lot' product_table_leg.tracking = 'lot' product_bolt.tracking = "lot" production_table_form = Form(self.env['mrp.production']) production_table_form.product_id = dining_table production_table_form.bom_id = mrp_bom_desk production_table_form.product_qty = 1.0 production_table_form.product_uom_id = dining_table.uom_id production_table = production_table_form.save() production_table.action_confirm() # Initial inventory of product sheet, lags and bolt lot_sheet = self.env['stock.lot'].create({'product_id': product_table_sheet.id, 'company_id': self.env.company.id}) lot_leg = self.env['stock.lot'].create({'product_id': product_table_leg.id, 'company_id': self.env.company.id}) lot_bolt = self.env['stock.lot'].create({'product_id': product_bolt.id, 'company_id': self.env.company.id}) # Initialize inventory # -------------------- quants = self.env['stock.quant'].create({ 'product_id': product_table_sheet.id, 'inventory_quantity': 20, 'lot_id': lot_sheet.id, 'location_id': self.source_location_id }) quants |= self.env['stock.quant'].create({ 'product_id': product_table_leg.id, 'inventory_quantity': 20, 'lot_id': lot_leg.id, 'location_id': self.source_location_id }) quants |= self.env['stock.quant'].create({ 'product_id': product_bolt.id, 'inventory_quantity': 20, 'lot_id': lot_bolt.id, 'location_id': self.source_location_id }) quants |= self.env['stock.quant'].create({ 'product_id': product_screw.id, 'inventory_quantity': 20, 'location_id': self.source_location_id }) quants.action_apply_inventory() # Check Work order created or not self.assertEqual(len(production_table.workorder_ids), 1) # --------------------------------------------------------- # Process all workorder and check it state. # ---------------------------------------------------------- workorder = production_table.workorder_ids[0] self.assertEqual(workorder.state, 'waiting', "workorder state should be waiting.") # -------------------------------------------------------------- # Process assembly line # --------------------------------------------------------- finished_lot = self.env['stock.lot'].create({'product_id': production_table.product_id.id, 'company_id': self.env.company.id}) workorder.write({'finished_lot_id': finished_lot.id}) workorder.button_start() workorder.qty_producing = 1.0 for quality_check in workorder.check_ids: if quality_check.component_id.id == product_bolt.id: quality_check.write({'lot_id': lot_bolt.id, 'qty_done': 1}) workorder.current_quality_check_id._next() if quality_check.component_id.id == product_table_sheet.id: quality_check.write({'lot_id': lot_sheet.id, 'qty_done': 1}) workorder.current_quality_check_id._next() if quality_check.component_id.id == product_table_leg.id: quality_check.write({'lot_id': lot_leg.id, 'qty_done': 1}) workorder.current_quality_check_id._next() self.assertEqual(workorder.state, 'progress') workorder.record_production() self.assertEqual(workorder.state, 'done') move_table_sheet = production_table.move_raw_ids.filtered(lambda x: x.product_id == product_table_sheet) self.assertEqual(move_table_sheet.quantity_done, 1) # --------------------------------------------------------------- # Check consume quants and produce quants after posting inventory # --------------------------------------------------------------- production_table.button_mark_done() self.assertEqual(product_screw.qty_available, 10) self.assertEqual(product_bolt.qty_available, 19) self.assertEqual(product_table_leg.qty_available, 19) self.assertEqual(product_table_sheet.qty_available, 19) def test_00b_workorder_process(self): """ Testing consume quants and produced quants with workorder """ dining_table = self.dining_table product_table_sheet = self.product_table_sheet product_table_leg = self.product_table_leg product_bolt = self.product_bolt # Set tracking lot on finish and consume products. dining_table.tracking = 'lot' product_table_sheet.tracking = 'lot' product_table_leg.tracking = 'lot' product_bolt.tracking = "lot" bom = self.mrp_bom_desk self.env['stock.move'].search([('product_id', '=', product_bolt.id)])._do_unreserve() bom.operation_ids = False bom.write({ 'operation_ids': [ (0, 0, { 'workcenter_id': self.mrp_workcenter_1.id, 'name': 'Packing', 'time_cycle': 30, 'sequence': 5}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Testing', 'time_cycle': 60, 'sequence': 10}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Long time assembly', 'time_cycle': 180, 'sequence': 15}), ] }) bom.bom_line_ids.filtered(lambda p: p.product_id == product_table_sheet).operation_id = bom.operation_ids[0] bom.bom_line_ids.filtered(lambda p: p.product_id == product_table_leg).operation_id = bom.operation_ids[1] bom.bom_line_ids.filtered(lambda p: p.product_id == product_bolt).operation_id = bom.operation_ids[2] production_table_form = Form(self.env['mrp.production']) production_table_form.product_id = dining_table production_table_form.bom_id = bom production_table_form.product_qty = 2.0 production_table_form.product_uom_id = dining_table.uom_id production_table = production_table_form.save() production_table.action_confirm() # Initial inventory of product sheet, lags and bolt lot_sheet = self.env['stock.lot'].create({'product_id': product_table_sheet.id, 'company_id': self.env.company.id}) lot_leg = self.env['stock.lot'].create({'product_id': product_table_leg.id, 'company_id': self.env.company.id}) lot_bolt = self.env['stock.lot'].create({'product_id': product_bolt.id, 'company_id': self.env.company.id}) # Initialize inventory # -------------------- self.env['stock.quant'].create({ 'product_id': product_table_sheet.id, 'inventory_quantity': 20, 'lot_id': lot_sheet.id, 'location_id': self.source_location_id }).action_apply_inventory() self.env['stock.quant'].create({ 'product_id': product_table_leg.id, 'inventory_quantity': 20, 'lot_id': lot_leg.id, 'location_id': self.source_location_id }).action_apply_inventory() self.env['stock.quant'].create({ 'product_id': product_bolt.id, 'inventory_quantity': 20, 'lot_id': lot_bolt.id, 'location_id': self.source_location_id }).action_apply_inventory() # Create work order production_table.button_plan() # Check Work order created or not self.assertEqual(len(production_table.workorder_ids), 3) # --------------------------------------------------------- # Process all workorder and check it state. # ---------------------------------------------------------- workorders = production_table.workorder_ids self.assertEqual(workorders[0].state, 'waiting', "First workorder state should be waiting.") self.assertEqual(workorders[1].state, 'pending') self.assertEqual(workorders[2].state, 'pending') # -------------------------------------------------------------- # Process cutting operation... # --------------------------------------------------------- finished_lot = self.env['stock.lot'].create({'product_id': production_table.product_id.id, 'company_id': self.env.company.id}) workorders[0].write({'finished_lot_id': finished_lot.id, 'qty_producing': 1.0}) workorders[0].button_start() workorders[0].write({'lot_id': lot_sheet.id, 'qty_done': 1}) workorders[0].current_quality_check_id._next() self.assertEqual(workorders[0].state, 'progress') workorders[0].record_production() move_table_sheet = production_table.move_raw_ids.filtered(lambda p: p.product_id == product_table_sheet) self.assertEqual(move_table_sheet.quantity_done, 1) # -------------------------------------------------------------- # Process drilling operation ... # --------------------------------------------------------- workorders[1].button_start() workorders[1].write({'lot_id': lot_leg.id, 'qty_done': 4}) workorders[1].current_quality_check_id._next() workorders[1].record_production() move_leg = production_table.move_raw_ids.filtered(lambda p: p.product_id == product_table_leg) self.assertEqual(workorders[1].state, 'done') self.assertEqual(move_leg.quantity_done, 4) # -------------------------------------------------------------- # Process fitting operation ... # --------------------------------------------------------- workorders[2].button_start() workorders[2].qty_producing = 1.0 workorders[2].write({'lot_id': lot_bolt.id, 'qty_done': 4}) workorders[2].current_quality_check_id._next() move_table_bolt = production_table.move_raw_ids.filtered(lambda p: p.product_id.id == product_bolt.id) workorders[2].record_production() self.assertEqual(move_table_bolt.quantity_done, 4) # Change the quantity of the production order to 1 wiz = self.env['change.production.qty'].create({ 'mo_id': production_table.id, 'product_qty': 1.0 }) wiz.change_prod_qty() # --------------------------------------------------------------- # Check consume quants and produce quants after posting inventory # --------------------------------------------------------------- production_table._post_inventory() self.assertEqual(sum(move_table_sheet.mapped('quantity_done')), 1, "Wrong quantity of consumed product %s" % move_table_sheet.product_id.name) self.assertEqual(sum(move_leg.mapped('quantity_done')), 4, "Wrong quantity of consumed product %s" % move_leg.product_id.name) self.assertEqual(sum(move_table_bolt.mapped('quantity_done')), 4, "Wrong quantity of consumed product %s" % move_table_bolt.product_id.name) def test_explode_from_order(self): # bom3 produces 2 Dozen of Doors (p6), aka 24 # To produce 24 Units of Doors (p6) # - 2 Units of Tools (p5) -> need 4 # - 8 Dozen of Sticks (p4) -> need 16 # - 12 Units of Wood (p2) -> need 24 # bom2 produces 1 Unit of Tools (p5) # To produce 1 Unit of Tools (p5) # - 2 Dozen of Sticks (p4) -> need 8 # - 3 Dozen of Stones (p3) -> need 12 # Update capacity, start time, stop time, and time efficiency. # ------------------------------------------------------------ self.workcenter_1.write({'default_capacity': 1, 'time_start': 0, 'time_stop': 0, 'time_efficiency': 100}) # Set manual time cycle 20 and 10. # -------------------------------- self.bom_3.operation_ids[0].time_cycle_manual = 10 self.bom_3.operation_ids[1].time_cycle_manual = 10 self.bom_2.operation_ids.time_cycle_manual = 20 man_order_form = Form(self.env['mrp.production']) man_order_form.product_id = self.product_6 man_order_form.bom_id = self.bom_3 man_order_form.product_qty = 48 man_order_form.product_uom_id = self.product_6.uom_id man_order = man_order_form.save() # reset quantities self.product_1.type = "product" self.env['stock.quant'].with_context(inventory_mode=True).create({ 'product_id': self.product_1.id, 'inventory_quantity': 0.0, 'location_id': self.warehouse_1.lot_stock_id.id, }) (self.product_2 | self.product_4).write({ 'tracking': 'none', }) # assign consume material man_order.action_confirm() man_order.action_assign() self.assertEqual(man_order.reservation_state, 'confirmed', "Production order should be in waiting state.") # check consume materials of manufacturing order self.assertEqual(len(man_order.move_raw_ids), 4, "Consume material lines are not generated properly.") product_2_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_2) product_3_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_3) product_4_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_4) product_5_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_5) consume_qty_2 = product_2_consume_moves.product_uom_qty self.assertEqual(consume_qty_2, 24.0, "Consume material quantity of Wood should be 24 instead of %s" % str(consume_qty_2)) consume_qty_3 = product_3_consume_moves.product_uom_qty self.assertEqual(consume_qty_3, 12.0, "Consume material quantity of Stone should be 12 instead of %s" % str(consume_qty_3)) self.assertEqual(len(product_4_consume_moves), 2, "Consume move are not generated properly.") self.assertEqual(sum(product_4_consume_moves.mapped('product_uom_qty')), 24, "Consume material quantity of Stick should be 24.") self.assertFalse(product_5_consume_moves, "Move should not create for phantom bom") # create required lots lot_product_2 = self.env['stock.lot'].create({'product_id': self.product_2.id, 'company_id': self.env.company.id}) lot_product_4 = self.env['stock.lot'].create({'product_id': self.product_4.id, 'company_id': self.env.company.id}) # refuel stock self.env['stock.quant'].create({ 'product_id': self.product_2.id, 'inventory_quantity': 30, 'lot_id': lot_product_2.id, 'location_id': self.stock_location_14.id }).action_apply_inventory() self.env['stock.quant'].create({ 'product_id': self.product_3.id, 'inventory_quantity': 60, 'location_id': self.stock_location_14.id }).action_apply_inventory() self.env['stock.quant'].create({ 'product_id': self.product_4.id, 'inventory_quantity': 60, 'lot_id': lot_product_4.id, 'location_id': self.stock_location_14.id }).action_apply_inventory() # re-assign consume material man_order.action_assign() # Check production order status after assign. self.assertEqual(man_order.reservation_state, 'assigned', "Production order should be in assigned state.") # Plan production order. man_order.button_plan() # check workorders # - main bom: Door: 2 operations # operation 1: Cutting # operation 2: Welding, waiting for the previous one # - kit bom: Stone Tool: 1 operation # operation 1: Gift Wrapping workorders = man_order.workorder_ids kit_wo = man_order.workorder_ids.filtered(lambda wo: wo.operation_id.name == "Gift Wrap Maching") door_wo_1 = man_order.workorder_ids.filtered(lambda wo: wo.operation_id.name == "Cutting Machine") door_wo_2 = man_order.workorder_ids.filtered(lambda wo: wo.operation_id.name == "Weld Machine") workorders._compute_state() for workorder in workorders: self.assertEqual(workorder.workcenter_id, self.workcenter_1, "Workcenter does not match.") self.assertEqual(kit_wo.state, 'ready', "Workorder should be in ready state.") self.assertEqual(door_wo_1.state, 'ready', "Workorder should be in ready state.") self.assertEqual(door_wo_2.state, 'pending', "Workorder should be in pending state.") self.assertEqual(kit_wo.duration_expected, 960, "Workorder duration should be 960 instead of %s." % str(kit_wo.duration_expected)) self.assertEqual(door_wo_1.duration_expected, 480, "Workorder duration should be 480 instead of %s." % str(door_wo_1.duration_expected)) self.assertEqual(door_wo_2.duration_expected, 480, "Workorder duration should be 480 instead of %s." % str(door_wo_2.duration_expected)) # subbom: kit for stone tools kit_wo.button_start() finished_lot = self.env['stock.lot'].create({'product_id': man_order.product_id.id, 'company_id': self.env.company.id}) kit_wo.write({ 'finished_lot_id': finished_lot.id, 'qty_producing': 48 }) kit_wo.record_production() self.assertEqual(kit_wo.state, 'done', "Workorder should be in done state.") # first operation of main bom finished_lot = self.env['stock.lot'].create({'product_id': man_order.product_id.id, 'company_id': self.env.company.id}) door_wo_1.button_start() door_wo_1.write({ 'finished_lot_id': finished_lot.id, 'qty_producing': 48 }) door_wo_1.record_production() self.assertEqual(door_wo_1.state, 'done', "Workorder should be in done state.") # second operation of main bom self.assertEqual(door_wo_2.state, 'ready', "Workorder should be in ready state.") door_wo_2.button_start() door_wo_2.record_production() self.assertEqual(door_wo_2.state, 'done', "Workorder should be in done state.") def test_01_without_workorder(self): """ Testing consume quants and produced quants without workorder """ unit = self.ref("uom.product_uom_unit") custom_laptop = self.env['product.product'].create({ 'name': 'Drawer', 'type': 'product', 'tracking': 'lot', }) # Create new product charger and keybord # -------------------------------------- product_charger = self.env['product.product'].create({ 'name': 'Charger', 'type': 'product', 'tracking': 'lot', 'uom_id': unit, 'uom_po_id': unit}) product_keybord = self.env['product.product'].create({ 'name': 'Usb Keybord', 'type': 'product', 'tracking': 'lot', 'uom_id': unit, 'uom_po_id': unit}) # Create bill of material for customized laptop. bom_custom_laptop = self.env['mrp.bom'].create({ 'product_tmpl_id': custom_laptop.product_tmpl_id.id, 'product_qty': 10, 'product_uom_id': unit, 'consumption': 'flexible', 'bom_line_ids': [(0, 0, { 'product_id': product_charger.id, 'product_qty': 20, 'product_uom_id': unit }), (0, 0, { 'product_id': product_keybord.id, 'product_qty': 20, 'product_uom_id': unit })] }) # Create production order for customize laptop. mo_custom_laptop_form = Form(self.env['mrp.production']) mo_custom_laptop_form.product_id = custom_laptop mo_custom_laptop_form.bom_id = bom_custom_laptop mo_custom_laptop_form.product_qty = 10.0 mo_custom_laptop_form.product_uom_id = self.env.ref("uom.product_uom_unit") mo_custom_laptop = mo_custom_laptop_form.save() mo_custom_laptop.action_confirm() # Assign component to production order. mo_custom_laptop.action_assign() # Check production order status of availablity self.assertEqual(mo_custom_laptop.reservation_state, 'confirmed') # -------------------------------------------------- # Set inventory for rawmaterial charger and keybord # -------------------------------------------------- lot_charger = self.env['stock.lot'].create({'product_id': product_charger.id, 'company_id': self.env.company.id}) lot_keybord = self.env['stock.lot'].create({'product_id': product_keybord.id, 'company_id': self.env.company.id}) # Initialize Inventory # -------------------- self.env['stock.quant'].create({ 'product_id': product_charger.id, 'inventory_quantity': 20, 'lot_id': lot_charger.id, 'location_id': self.source_location_id }).action_apply_inventory() self.env['stock.quant'].create({ 'product_id': product_keybord.id, 'inventory_quantity': 20, 'lot_id': lot_keybord.id, 'location_id': self.source_location_id }).action_apply_inventory() # Check consumed move status mo_custom_laptop.action_assign() self.assertEqual(mo_custom_laptop.reservation_state, 'assigned') # Check current status of raw materials. for move in mo_custom_laptop.move_raw_ids: self.assertEqual(move.product_uom_qty, 20, "Wrong consume quantity of raw material %s: %s instead of %s" % (move.product_id.name, move.product_uom_qty, 20)) self.assertEqual(move.quantity_done, 0, "Wrong produced quantity on raw material %s: %s instead of %s" % (move.product_id.name, move.quantity_done, 0)) # ----------------- # Start production # ----------------- # Produce 6 Unit of custom laptop will consume ( 12 Unit of keybord and 12 Unit of charger) laptop_lot_001 = self.env['stock.lot'].create({'product_id': custom_laptop.id, 'company_id': self.env.company.id}) mo_form = Form(mo_custom_laptop) mo_form.qty_producing = 6 mo_form.lot_producing_id = laptop_lot_001 mo_custom_laptop = mo_form.save() details_operation_form = Form(mo_custom_laptop.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) with details_operation_form.move_line_ids.edit(0) as ml: ml.qty_done = 12 details_operation_form.save() details_operation_form = Form(mo_custom_laptop.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations')) with details_operation_form.move_line_ids.edit(0) as ml: ml.qty_done = 12 details_operation_form.save() action = mo_custom_laptop.button_mark_done() backorder = Form(self.env[action['res_model']].with_context(**action['context'])) backorder.save().action_backorder() # Check consumed move after produce 6 quantity of customized laptop. for move in mo_custom_laptop.move_raw_ids: self.assertEqual(move.quantity_done, 12, "Wrong produced quantity on raw material %s" % (move.product_id.name)) self.assertEqual(len(mo_custom_laptop.move_raw_ids), 2) # Check done move and confirmed move quantity. charger_done_move = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_charger.id and x.state == 'done') keybord_done_move = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_keybord.id and x.state == 'done') self.assertEqual(charger_done_move.product_uom_qty, 12) self.assertEqual(keybord_done_move.product_uom_qty, 12) # Produce remaining 4 quantity # ---------------------------- # Produce 4 Unit of custom laptop will consume ( 8 Unit of keybord and 8 Unit of charger). laptop_lot_002 = self.env['stock.lot'].create({'product_id': custom_laptop.id, 'company_id': self.env.company.id}) mo_custom_laptop = mo_custom_laptop.procurement_group_id.mrp_production_ids[1] mo_form = Form(mo_custom_laptop) mo_form.qty_producing = 4 mo_form.lot_producing_id = laptop_lot_002 mo_custom_laptop = mo_form.save() details_operation_form = Form(mo_custom_laptop.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) with details_operation_form.move_line_ids.edit(0) as ml: ml.qty_done = 8 details_operation_form.save() details_operation_form = Form(mo_custom_laptop.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations')) with details_operation_form.move_line_ids.edit(0) as ml: ml.qty_done = 8 details_operation_form.save() charger_move = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_charger.id and x.state != 'done') keybord_move = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_keybord.id and x.state !='done') self.assertEqual(charger_move.quantity_done, 8, "Wrong consumed quantity of %s" % charger_move.product_id.name) self.assertEqual(keybord_move.quantity_done, 8, "Wrong consumed quantity of %s" % keybord_move.product_id.name) def test_03_test_serial_number_defaults(self): """ Test that the correct serial number is suggested on consecutive work orders. """ laptop = self.laptop graphics_card = self.graphics_card unit = self.env.ref("uom.product_uom_unit") laptop.tracking = 'serial' bom_laptop = self.env['mrp.bom'].create({ 'product_tmpl_id': laptop.product_tmpl_id.id, 'product_qty': 1, 'product_uom_id': unit.id, 'consumption': 'flexible', 'bom_line_ids': [(0, 0, { 'product_id': graphics_card.id, 'product_qty': 1, 'product_uom_id': unit.id })], 'operation_ids': [ (0, 0, { 'workcenter_id': self.mrp_workcenter_1.id, 'name': 'Packing', 'time_cycle': 30, 'sequence': 5}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Testing', 'time_cycle': 60, 'sequence': 10}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Long time assembly', 'time_cycle': 180, 'sequence': 15}), ], }) mo_laptop_form = Form(self.env['mrp.production']) mo_laptop_form.product_id = laptop mo_laptop_form.bom_id = bom_laptop mo_laptop_form.product_qty = 3 mo_laptop = mo_laptop_form.save() mo_laptop.action_confirm() mo_laptop.button_plan() workorders = mo_laptop.workorder_ids.sorted() self.assertEqual(len(workorders), 3) workorders[0].open_tablet_view() workorder = workorders[0] serial_a = self.env['stock.lot'].create({'product_id': laptop.id, 'company_id': self.env.company.id}) workorder.finished_lot_id = serial_a workorder = self.env['mrp.workorder'].browse(workorder.record_production()['res_id']) serial_b = self.env['stock.lot'].create({'product_id': laptop.id, 'company_id': self.env.company.id}) workorder.finished_lot_id = serial_b workorder = self.env['mrp.workorder'].browse(workorder.record_production()['res_id']) serial_c = self.env['stock.lot'].create({'product_id': laptop.id, 'company_id': self.env.company.id}) workorder.finished_lot_id = serial_c workorder.record_production() self.assertEqual(workorders[0].state, 'done') for workorder in workorders - workorders[0]: workorder.button_start() self.assertEqual(workorder.finished_lot_id, serial_a) workorder = self.env['mrp.workorder'].browse(workorder.record_production()['res_id']) self.assertEqual(workorder.finished_lot_id, serial_b) workorder = self.env['mrp.workorder'].browse(workorder.record_production()['res_id']) self.assertEqual(workorder.finished_lot_id, serial_c) workorder.record_production() self.assertEqual(workorder.state, 'done') def test_03b_test_serial_number_defaults(self): """ Check the constraint on the workorder final_lot. The first workorder produces 2/2 units without serial number (serial is only required when you register a component) then the second workorder try to register a serial number. It should be allowed since the first workorder did not specify a seiral number. """ drawer = self.env['product.product'].create({ 'name': 'Drawer', 'type': 'product', 'tracking': 'lot', }) drawer_drawer = self.env['product.product'].create({ 'name': 'Drawer Black', 'type': 'product', 'tracking': 'lot', }) drawer_case = self.env['product.product'].create({ 'name': 'Drawer Case Black', 'type': 'product', 'tracking': 'lot', }) bom = self.env['mrp.bom'].create({ 'product_tmpl_id': drawer.product_tmpl_id.id, 'product_uom_id': self.env.ref('uom.product_uom_unit').id, 'consumption': 'flexible', 'sequence': 2, 'operation_ids': [ (0, 0, { 'workcenter_id': self.mrp_workcenter_1.id, 'name': 'Packing', 'time_cycle': 30, 'sequence': 5}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Testing', 'time_cycle': 60, 'sequence': 10}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Long time assembly', 'time_cycle': 180, 'sequence': 15}), ], 'bom_line_ids': [(0, 0, { 'product_id': drawer_drawer.id, 'product_qty': 1, 'product_uom_id': self.env.ref('uom.product_uom_unit').id, 'sequence': 1, }), (0, 0, { 'product_id': drawer_case.id, 'product_qty': 1, 'product_uom_id': self.env.ref('uom.product_uom_unit').id, 'sequence': 2, })] }) self.env['stock.quant'].create({ 'product_id': drawer_drawer.id, 'inventory_quantity': 50.0, 'location_id': self.stock_location_14.id }).action_apply_inventory() self.env['stock.quant'].create({ 'product_id': drawer_case.id, 'inventory_quantity': 50.0, 'location_id': self.stock_location_14.id }).action_apply_inventory() product = bom.product_tmpl_id.product_variant_id product.tracking = 'serial' lot_1 = self.env['stock.lot'].create({ 'product_id': product.id, 'name': 'LOT000001', 'company_id': self.env.company.id, }) lot_2 = self.env['stock.lot'].create({ 'product_id': product.id, 'name': 'LOT000002', 'company_id': self.env.company.id, }) self.env['stock.lot'].create({ 'product_id': product.id, 'name': 'LOT000003', 'company_id': self.env.company.id, }) mo_form = Form(self.env['mrp.production']) mo_form.product_id = product mo_form.bom_id = bom mo_form.product_qty = 2.0 mo = mo_form.save() mo.action_confirm() mo.button_plan() workorder_0 = mo.workorder_ids[0] workorder_0.open_tablet_view() workorder_0 = self.env['mrp.workorder'].browse(workorder_0.record_production()['res_id']) workorder_0.record_production() workorder_1 = mo.workorder_ids[1] workorder_1.button_start() # `finished_lot_id` is invisible in the view # however, it's a computed field # `workorder.finished_lot_id = workorder.production_id.lot_producing_id` with Form(workorder_1.production_id) as wo_production: wo_production.lot_producing_id = lot_1 workorder_1 = self.env['mrp.workorder'].browse(workorder_1.record_production()['res_id']) with Form(workorder_1.production_id) as wo_production: wo_production.lot_producing_id = lot_2 workorder_1.record_production() workorder_2 = mo.workorder_ids[2] self.assertEqual(workorder_2.finished_lot_id, lot_1) productions = workorder_1.production_id.procurement_group_id.mrp_production_ids self.assertEqual(sum(productions.mapped('qty_producing')), 2) self.assertEqual(productions.lot_producing_id, lot_1 | lot_2) for production in productions: production._post_inventory() self.assertEqual(sum(productions.move_finished_ids.move_line_ids.mapped('qty_done')), 2) self.assertEqual(productions.move_finished_ids.move_line_ids.mapped('lot_id'), lot_1 | lot_2) def test_04_test_planning_date(self): """ Test that workorder are planned at the correct time. """ # The workcenter is working 24/7 self.full_availability() dining_table = self.dining_table production_table_form = Form(self.env['mrp.production']) production_table_form.product_id = dining_table production_table_form.bom_id = self.mrp_bom_desk production_table_form.product_qty = 1.0 production_table_form.product_uom_id = dining_table.uom_id production_table = production_table_form.save() production_table.action_confirm() # Create work order production_table.button_plan() workorder = production_table.workorder_ids[0] # Check that the workorder is planned now and that it lasts one hour self.assertAlmostEqual(workorder.date_planned_start, datetime.now(), delta=timedelta(seconds=10), msg="Workorder should be planned now.") self.assertAlmostEqual(workorder.date_planned_finished, datetime.now() + timedelta(hours=1), delta=timedelta(seconds=10), msg="Workorder should be done in an hour.") def test_04b_test_planning_date(self): """ Test that workorder are planned at the correct time when setting a start date """ # The workcenter is working 24/7 self.full_availability() dining_table = self.dining_table date_start = datetime.now() + timedelta(days=1) production_table_form = Form(self.env['mrp.production']) production_table_form.product_id = dining_table production_table_form.bom_id = self.mrp_bom_desk production_table_form.product_qty = 1.0 production_table_form.product_uom_id = dining_table.uom_id production_table_form.date_planned_start = date_start production_table = production_table_form.save() production_table.action_confirm() # Create work order production_table.button_plan() workorder = production_table.workorder_ids[0] # Check that the workorder is planned now and that it lasts one hour self.assertAlmostEqual(workorder.date_planned_start, date_start, delta=timedelta(seconds=1), msg="Workorder should be planned tomorrow.") self.assertAlmostEqual(workorder.date_planned_finished, date_start + timedelta(hours=1), delta=timedelta(seconds=1), msg="Workorder should be done one hour later.") def test_unlink_workorder(self): drawer = self.env['product.product'].create({ 'name': 'Drawer', 'type': 'product', 'tracking': 'lot', }) drawer_drawer = self.env['product.product'].create({ 'name': 'Drawer Black', 'type': 'product', 'tracking': 'lot', }) drawer_case = self.env['product.product'].create({ 'name': 'Drawer Case Black', 'type': 'product', 'tracking': 'lot', }) bom = self.env['mrp.bom'].create({ 'product_tmpl_id': drawer.product_tmpl_id.id, 'product_uom_id': self.env.ref('uom.product_uom_unit').id, 'consumption': 'flexible', 'sequence': 2, 'operation_ids': [ (0, 0, { 'workcenter_id': self.mrp_workcenter_1.id, 'name': 'Packing', 'time_cycle': 30, 'sequence': 5}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Testing', 'time_cycle': 60, 'sequence': 10}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Long time assembly', 'time_cycle': 180, 'sequence': 15}), ], 'bom_line_ids': [(0, 0, { 'product_id': drawer_drawer.id, 'product_qty': 1, 'product_uom_id': self.env.ref('uom.product_uom_unit').id, 'sequence': 1, }), (0, 0, { 'product_id': drawer_case.id, 'product_qty': 1, 'product_uom_id': self.env.ref('uom.product_uom_unit').id, 'sequence': 2, })] }) production_table_form = Form(self.env['mrp.production']) production_table_form.product_id = drawer production_table_form.bom_id = bom production_table_form.product_qty = 2.0 production_table_form.product_uom_id = drawer.uom_id production_table = production_table_form.save() production_table.action_confirm() production_table.button_plan() self.assertEqual(len(production_table.workorder_ids), 3) workorders = production_table.workorder_ids for i in range(len(workorders) - 1): self.assertEqual(workorders[i].needed_by_workorder_ids, workorders[i + 1]) production_table.workorder_ids[1].unlink() self.assertEqual(len(production_table.workorder_ids), 2) workorders = production_table.workorder_ids for i in range(len(workorders) - 1): self.assertEqual(workorders[i].needed_by_workorder_ids, workorders[i + 1]) def test_planning_overlaps_wo(self): """ Test that workorder doesn't overlaps between then when plan the MO """ self.full_availability() dining_table = self.dining_table # Take between +30min -> +90min date_start = datetime.now() + timedelta(minutes=30) production_table_form = Form(self.env['mrp.production']) production_table_form.product_id = dining_table production_table_form.bom_id = self.mrp_bom_desk production_table_form.product_qty = 1.0 production_table_form.product_uom_id = dining_table.uom_id production_table_form.date_planned_start = date_start production_table = production_table_form.save() production_table.action_confirm() # Create work order production_table.button_plan() workorder_prev = production_table.workorder_ids[0] # Check that the workorder is planned now and that it lasts one hour self.assertAlmostEqual(workorder_prev.date_planned_start, date_start, delta=timedelta(seconds=10), msg="Workorder should be planned in +30min") self.assertAlmostEqual(workorder_prev.date_planned_finished, date_start + timedelta(hours=1), delta=timedelta(seconds=10), msg="Workorder should be done in +90min") # As soon as possible, but because of the first one, it will planned only after +90 min date_start = datetime.now() production_table_form = Form(self.env['mrp.production']) production_table_form.product_id = dining_table production_table_form.bom_id = self.mrp_bom_desk production_table_form.product_qty = 1.0 production_table_form.product_uom_id = dining_table.uom_id production_table_form.date_planned_start = date_start production_table = production_table_form.save() production_table.action_confirm() # Create work order production_table.button_plan() workorder = production_table.workorder_ids[0] # Check that the workorder is planned now and that it lasts one hour self.assertAlmostEqual(workorder.date_planned_start, workorder_prev.date_planned_finished, delta=timedelta(seconds=10), msg="Workorder should be planned after the first one") self.assertAlmostEqual(workorder.date_planned_finished, workorder_prev.date_planned_finished + timedelta(hours=1), delta=timedelta(seconds=10), msg="Workorder should be done one hour later.") @freeze_time('2021-12-01') def test_planning_overlaps_wo_02(self): """ Test that workorder doesn't overlaps between then when plan the MO here is the expected result: MO1 : 01/dec/2021 00:00 → 01/dec/2021 00:24 MO2 : 01/dec/2021 00:24 → 01/dec/2021 00:48 MO3 : 02/dec/2021 00:00 → 02/dec/2021 00:24 MO4 : 02/dec/2021 00:24 → 02/dec/2021 00:48 MO5 : 02/dec/2021 00:48 → 03/dec/2021 00:12 """ self.full_availability() calendar = self.env['resource.calendar'].search([]) # Possible working hours: every day from 00:00 to 01:00 (UTC) calendar.attendance_ids.hour_to = 1 calendar.tz = 'UTC' dining_table = self.dining_table # Set the work order duration to 24 minutes dining_table.bom_ids.operation_ids[0].time_cycle_manual = 24 # Set the date_start to 01/12/2021 00:00 date_start = datetime.today() # Create 5 MO. one of them has a different date from the others all_production = self.env['mrp.production'] for date in [date_start, date_start, (date_start + timedelta(days=1)), date_start, date_start]: production_table_form = Form(self.env['mrp.production']) production_table_form.product_id = dining_table production_table_form.bom_id = self.mrp_bom_desk production_table_form.product_qty = 1.0 production_table_form.product_uom_id = dining_table.uom_id production_table_form.date_planned_start = date all_production += production_table_form.save() all_production.action_confirm() all_production.button_plan() workorder_a = all_production[3].workorder_ids[0] workorder_b = all_production[4].workorder_ids[0] # Check that the workorders are planned correctly and that it lasts 24 minutes self.assertAlmostEqual(workorder_a.date_planned_start, (date_start + timedelta(days=1, minutes=24)), delta=timedelta(seconds=10), msg="The workorder should be planned for the first available interval") self.assertAlmostEqual(workorder_a.date_planned_finished, (date_start + timedelta(days=1, minutes=48)), delta=timedelta(seconds=10), msg="Workorder should be done in 24 minutes") self.assertAlmostEqual(workorder_b.date_planned_start, (date_start + timedelta(days=1, minutes=48)), delta=timedelta(seconds=10), msg="The workorder should be planned for the first available interval") self.assertAlmostEqual(workorder_b.date_planned_finished, (date_start + timedelta(days=2, minutes=12)), delta=timedelta(seconds=10), msg="Workorder should be done in 24 minutes") def test_change_production_1(self): """Change the quantity to produce on the MO while workorders are already planned.""" dining_table = self.dining_table dining_table.tracking = 'lot' production_table_form = Form(self.env['mrp.production']) production_table_form.product_id = dining_table production_table_form.bom_id = self.mrp_bom_desk production_table_form.product_qty = 1.0 production_table_form.product_uom_id = dining_table.uom_id production_table = production_table_form.save() production_table.action_confirm() mo_form = Form(production_table) mo_form.qty_producing = 1 production_table = mo_form.save() # Create work order production_table.button_plan() context = {'active_id': production_table.id, 'active_model': 'mrp.production'} change_qty_form = Form(self.env['change.production.qty'].with_context(context)) change_qty_form.product_qty = 2.00 change_qty = change_qty_form.save() change_qty.change_prod_qty() self.assertEqual(production_table.workorder_ids[0].qty_producing, 2, "Quantity to produce not updated") def test_planning_0(self): """ Test alternative conditions 1. alternative relation is directionnal 2. a workcenter cannot be it's own alternative """ self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2 self.assertEqual(self.wc_alt_1.alternative_workcenter_ids, self.env['mrp.workcenter'], "Alternative workcenter is not reciprocal") self.assertEqual(self.wc_alt_2.alternative_workcenter_ids, self.env['mrp.workcenter'], "Alternative workcenter is not reciprocal") with self.assertRaises(ValidationError): self.workcenter_1.alternative_workcenter_ids |= self.workcenter_1 def test_planning_1(self): """ Testing planning workorder with alternative workcenters Plan 6 times the same MO, the workorders should be split across workcenters The 3 workcenters are free, this test plans 3 workorder in a row then three next. The workcenters have not exactly the same parameters (efficiency, start time) so the the last 3 workorder are not dispatched like the 3 first. At the end of the test, the calendars will look like: - calendar wc1 :[mo1][mo4] - calendar wc2 :[mo2 ][mo5 ] - calendar wc3 :[mo3 ][mo6 ]""" planned_date = datetime(2023, 5, 15, 9, 0) self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2 workcenters = [self.wc_alt_2, self.wc_alt_1, self.workcenter_1] for i in range(3): # Create an MO for product4 mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 mo_form.date_planned_start = planned_date mo = mo_form.save() mo.action_confirm() mo.button_plan() # Check that workcenters change self.assertEqual(mo.workorder_ids.workcenter_id, workcenters[i], "wrong workcenter %d" % i) 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)) for i in range(3): # Planning 3 more should choose workcenters in opposite order as # - wc_alt_2 as the best efficiency # - wc_alt_1 take a little less start time # - workcenter_1 is the worst mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 mo_form.date_planned_start = planned_date mo = mo_form.save() mo.action_confirm() mo.button_plan() # Check that workcenters change self.assertEqual(mo.workorder_ids.workcenter_id, workcenters[i], "wrong workcenter %d" % i) self.assertNotEqual(mo.date_planned_start, planned_date) self.assertAlmostEqual(mo.date_planned_start, mo.workorder_ids.date_planned_start, delta=timedelta(seconds=10)) def test_planning_3(self): """ Plan some manufacturing orders with 1 workorder on 1 workcenter the first workorder will be hard set in the future to see if the second one take the free slot before on the calendar calendar after first mo : [ ][mo1] calendar after second mo: [mo2][mo1] """ self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2 self.env['mrp.workcenter'].search([]).resource_calendar_id.write({'tz': 'UTC'}) # compute all date in UTC planned_date = datetime(2023, 5, 15, 14, 0) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 mo_form.date_planned_start = planned_date mo = mo_form.save() start = mo.date_planned_start mo.action_confirm() mo.button_plan() self.assertEqual(mo.workorder_ids[0].workcenter_id, self.wc_alt_2, "wrong workcenter") wo1_start = mo.workorder_ids[0].date_planned_start wo1_stop = mo.workorder_ids[0].date_planned_finished self.assertAlmostEqual(wo1_start, start, delta=timedelta(seconds=10), msg="Wrong plannification") self.assertAlmostEqual(wo1_stop, start + timedelta(minutes=85.58), delta=timedelta(seconds=10), msg="Wrong plannification") # second MO should be plan before as there is a free slot before planned_date = datetime(2023, 5, 15, 9, 0) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 mo_form.date_planned_start = planned_date mo = mo_form.save() mo.action_confirm() mo.button_plan() self.assertEqual(mo.workorder_ids[0].workcenter_id, self.wc_alt_2, "wrong workcenter") wo1_start = mo.workorder_ids[0].date_planned_start wo1_stop = mo.workorder_ids[0].date_planned_finished self.assertAlmostEqual(wo1_start, planned_date, delta=timedelta(seconds=10), msg="Wrong plannification") self.assertAlmostEqual(wo1_stop, planned_date + timedelta(minutes=85.59), delta=timedelta(seconds=10), msg="Wrong plannification") def test_planning_4(self): """ Plan a manufacturing orders with 1 workorder on 1 workcenter the workcenter calendar is empty. which means the workcenter is never available. Planning a workorder on it should raise an error""" self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2 self.env['resource.calendar'].search([]).write({'attendance_ids': [(5, False, False)]}) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 mo = mo_form.save() mo.action_confirm() with self.assertRaises(UserError): mo.button_plan() def test_planning_5(self): """ Cancelling a production with workorders should free all reserved slot in the related workcenters calendars """ mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 mo = mo_form.save() mo.action_confirm() mo.button_plan() mo.action_cancel() self.assertEqual(mo.workorder_ids.mapped('date_start'), [False]) self.assertEqual(mo.workorder_ids.mapped('date_finished'), [False]) def test_planning_6(self): """ Marking a workorder as done before the theoretical date should update the reservation slot in the calendar the be able to reserve the next production sooner """ self.env['mrp.workcenter'].search([]).write({'tz': 'UTC'}) # compute all date in UTC mrp_workcenter_3 = self.env['mrp.workcenter'].create({ 'name': 'assembly line 1', 'resource_calendar_id': self.env.ref('resource.resource_calendar_std').id, }) self.planning_bom.operation_ids = False self.planning_bom.write({ 'operation_ids': [(0, 0, { 'workcenter_id': mrp_workcenter_3.id, 'name': 'Manual Assembly', 'time_cycle': 60, })] }) self.planning_bom.operation_ids = False self.planning_bom.write({ 'operation_ids': [(0, 0, { 'workcenter_id': mrp_workcenter_3.id, 'name': 'Manual Assembly', 'time_cycle': 60, })] }) planned_date = datetime(2023, 5, 15, 9, 0) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 mo_form.date_planned_start = planned_date mo = mo_form.save() mo.action_confirm() mo.button_plan() wo = mo.workorder_ids self.assertAlmostEqual(wo.date_planned_start, planned_date, delta=timedelta(seconds=10)) self.assertAlmostEqual(wo.date_planned_finished, planned_date + timedelta(minutes=60), delta=timedelta(seconds=10)) wo.button_start() wo.qty_producing = 1.0 wo.record_production() # Marking workorder as done should change the finished date self.assertAlmostEqual(wo.date_finished, datetime.now(), delta=timedelta(seconds=10)) self.assertAlmostEqual(wo.date_planned_finished, datetime.now(), delta=timedelta(seconds=10)) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 mo_form.date_planned_start = planned_date mo = mo_form.save() mo.action_confirm() mo.button_plan() wo = mo.workorder_ids wo.button_start() self.assertAlmostEqual(wo.date_start, datetime.now(), delta=timedelta(seconds=10)) self.assertAlmostEqual(wo.date_planned_start, datetime.now(), delta=timedelta(seconds=10)) def test_planning_7(self): """ set the workcenter capacity to 10. Produce a dozen of product tracked by SN. The production should be done in two batches""" self.workcenter_1.default_capacity = 10 self.workcenter_1.time_efficiency = 100 self.workcenter_1.time_start = 0 self.workcenter_1.time_stop = 0 self.planning_bom.operation_ids.time_cycle = 60 self.product_4.tracking = 'serial' mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_uom_id = self.uom_dozen mo_form.product_qty = 1 mo = mo_form.save() mo.action_confirm() mo.button_plan() wo = mo.workorder_ids self.assertEqual(wo.duration_expected, 120) def test_planning_7_with_product_capacity(self): """ Set the workcenter capacity to 10 by default and 12 on product. Produce a dozen of product tracked by SN. The production should be done in only 1 batch -> Reproduce test_planning_7 with specific product capacity """ self.workcenter_1.default_capacity = 10 self.workcenter_1.capacity_ids = [Command.create({'product_id': self.product_4.id, 'capacity': 12})] self.workcenter_1.time_efficiency = 100 self.workcenter_1.time_start = 0 self.workcenter_1.time_stop = 0 self.planning_bom.operation_ids.time_cycle = 60 mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_uom_id = self.uom_dozen mo_form.product_qty = 1 mo = mo_form.save() mo.action_confirm() mo.button_plan() wo = mo.workorder_ids self.assertEqual(wo.duration_expected, 60) @freeze_time('2022-8-08') def test_planning_8(self): """ Plan a workorder and move it on the planning, the workorder duration should always be consistent with the planned start and finish date""" self.env['mrp.workcenter'].search([]).write({'tz': 'UTC'}) self.env['resource.calendar'].search([]).write({'tz': 'UTC'}) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 self.planning_bom.operation_ids.time_cycle_manual = 120.0 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 mo_form.date_planned_start = datetime(2022, 8, 8, 11, 0, 0, 0) mo = mo_form.save() mo.action_confirm() mo.button_plan() wo = mo.workorder_ids wc = wo.workcenter_id self.assertEqual(wo.date_planned_start, datetime(2022, 8, 8, 11, 0, 0, 0), "Date planned start should not have changed") self.assertEqual(wo.duration_expected, (120.0 * 100 / wc.time_efficiency) + wc.time_start + wc.time_stop, "Duration expected should be the sum of the time_cycle (taking the workcenter efficiency into account), the time_start and time_stop") self.assertEqual(wo.date_planned_finished, wo.date_planned_start + timedelta(minutes=wo.duration_expected+60), "Date planned finished should take into consideration the midday break") duration_expected = wo.duration_expected # Move the workorder in the planning so that it doesn't span across the midday break wo.write({'date_planned_start': datetime(2022, 8, 8, 9, 0, 0, 0), 'date_planned_finished': datetime(2022, 8, 8, 12, 45, 0, 0)}) self.assertEqual(wo.date_planned_start, datetime(2022, 8, 8, 9, 0, 0, 0), "Date planned start should have changed") self.assertEqual(wo.duration_expected, duration_expected, "Duration expected should not have changed") self.assertEqual(wo.date_planned_finished, wo.date_planned_start + timedelta(minutes=duration_expected), "Date planned finished should have been recomputed to be consistent with the workorder duration") date_planned_finished = wo.date_planned_finished # Extend workorder one hour to the left wo.write({'date_planned_start': datetime(2022, 8, 8, 8, 0, 0, 0)}) self.assertEqual(wo.date_planned_start, datetime(2022, 8, 8, 8, 0, 0, 0), "Date planned start should have changed") self.assertEqual(wo.duration_expected, duration_expected + 60, "Duration expected should have been extended by one hour") self.assertEqual(wo.date_planned_finished, date_planned_finished, "Date planned finished should not have changed") self.assertEqual(wo.date_planned_finished, wo.date_planned_start + timedelta(minutes=wo.duration_expected), "Date planned finished should be consistent with the workorder duration") duration_expected = wo.duration_expected # Extend workorder 2 hours to the right (span across the midday break) wo.write({'date_planned_finished': datetime(2022, 8, 8, 13, 45, 0, 0)}) self.assertEqual(wo.date_planned_start, datetime(2022, 8, 8, 8, 0, 0, 0), "Date planned start should not have changed") self.assertEqual(wo.duration_expected, duration_expected + 60, "Duration expected should have been extended by one hour") self.assertEqual(wo.date_planned_finished, datetime(2022, 8, 8, 13, 45, 0, 0), "Date planned finished should have changed") self.assertEqual(wo.date_planned_finished, wo.date_planned_start + timedelta(minutes=wo.duration_expected+60), "Date planned finished should be consistent with the workorder duration") def test_plan_unplan_date(self): """ Testing planning a workorder then cancel it and then plan it again. The planned date must be the same the first time and the second time the workorder is planned.""" planned_date = datetime(2023, 5, 15, 9, 0) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 mo_form.date_planned_start = planned_date mo = mo_form.save() mo.action_confirm() # Plans the MO and checks the date. mo.button_plan() self.assertAlmostEqual(mo.date_planned_start, planned_date, delta=timedelta(seconds=10)) self.assertEqual(bool(mo.workorder_ids.exists()), True) leave = mo.workorder_ids.leave_id self.assertEqual(bool(leave.exists()), True) # Unplans the MO and checks the workorder and its leave no more exist. mo.button_unplan() self.assertEqual(bool(mo.workorder_ids.exists()), True) self.assertEqual(bool(leave.exists()), False) # Plans (again) the MO and checks the date is still the same. 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)) def test_kit_planning(self): """ Bom made of component 1 and component 2 which is a kit made of component 1 too. Check the workorder lines are well created after reservation Main bom : - comp1 (qty=1) - kit (qty=1) - comp1 (qty=4) - comp2 (qty=1) should give : - wo line 1 (comp1, qty=1) - wo line 2 (comp1, qty=4) - wo line 3 (comp2, qty=1) """ # avoid having reservation issues by making all components consu self.product_2.type = 'consu' # Kit bom self.env['mrp.bom'].create({ 'product_id': self.product_4.id, 'product_tmpl_id': self.product_4.product_tmpl_id.id, 'product_uom_id': self.uom_unit.id, 'product_qty': 1.0, 'consumption': 'flexible', 'type': 'phantom', 'bom_line_ids': [ (0, 0, {'product_id': self.product_2.id, 'product_qty': 1}), (0, 0, {'product_id': self.product_1.id, 'product_qty': 4}) ]}) # Main bom main_bom = self.env['mrp.bom'].create({ 'product_id': self.product_5.id, 'product_tmpl_id': self.product_5.product_tmpl_id.id, 'product_uom_id': self.uom_unit.id, 'product_qty': 1.0, 'consumption': 'flexible', 'operation_ids': [ (0, 0, { 'workcenter_id': self.mrp_workcenter_1.id, 'name': 'Packing', 'time_cycle': 30, 'sequence': 5}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Testing', 'time_cycle': 60, 'sequence': 10}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Long time assembly', 'time_cycle': 180, 'sequence': 15}), ], 'type': 'normal', 'bom_line_ids': [ (0, 0, {'product_id': self.product_1.id, 'product_qty': 1}), (0, 0, {'product_id': self.product_4.id, 'product_qty': 1}) ]}) self.env['quality.point'].create({ 'product_ids': [(4, self.product_5.id)], 'picking_type_ids': [(4, self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1).id)], 'operation_id': main_bom.operation_ids[2].id, 'test_type_id': self.env.ref('mrp_workorder.test_type_register_consumed_materials').id, 'component_id': self.product_1.id, }) self.env['quality.point'].create({ 'product_ids': [(4, self.product_5.id)], 'picking_type_ids': [(4, self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1).id)], 'operation_id': main_bom.operation_ids[2].id, 'test_type_id': self.env.ref('mrp_workorder.test_type_register_consumed_materials').id, 'component_id': self.product_2.id, }) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_5 mo_form.bom_id = main_bom mo_form.product_qty = 1 mo = mo_form.save() mo.action_confirm() mo.action_assign() mo.button_plan() self.assertEqual(len(mo.workorder_ids), 3) long_time_assembly = mo.workorder_ids[2] # same component merged in one line self.assertEqual(len(long_time_assembly.check_ids), 3) line1 = long_time_assembly.check_ids[0] line2 = long_time_assembly.check_ids[1] line3 = long_time_assembly.check_ids[2] self.assertEqual(line1.component_id, self.product_1) self.assertEqual(line1.qty_done, 1) self.assertEqual(line2.component_id, self.product_1) self.assertEqual(line2.qty_done, 4) self.assertEqual(line3.component_id, self.product_2) self.assertEqual(line3.qty_done, 1) def test_conflict_and_replan(self): """ TEST Json data conflicted and the replan button of a workorder """ dining_table = self.dining_table bom = self.mrp_bom_desk bom.operation_ids = False bom.write({ 'operation_ids': [ (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Packing', 'time_cycle': 30, 'sequence': 5}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Testing', 'time_cycle': 60, 'sequence': 10}), (0, 0, { 'workcenter_id': self.mrp_workcenter_3.id, 'name': 'Long time assembly', 'time_cycle': 180, 'sequence': 15}), ]}) bom.bom_line_ids.filtered(lambda p: p.product_id == self.product_table_sheet).operation_id = bom.operation_ids[0].id bom.bom_line_ids.filtered(lambda p: p.product_id == self.product_table_leg).operation_id = bom.operation_ids[1].id bom.bom_line_ids.filtered(lambda p: p.product_id == self.product_bolt).operation_id = bom.operation_ids[2].id production_table_form = Form(self.env['mrp.production']) production_table_form.product_id = dining_table production_table_form.bom_id = bom production_table_form.product_qty = 1.0 production_table_form.product_uom_id = dining_table.uom_id production_table = production_table_form.save() production_table.action_confirm() # Create work order production_table.button_plan() # Check Work order created or not self.assertEqual(len(production_table.workorder_ids), 3) workorders = production_table.workorder_ids wo1, wo2, wo3 = workorders[0], workorders[1], workorders[2] self.assertEqual(wo1.state, 'waiting', "First workorder state should be ready.") self.assertEqual(wo1.workcenter_id.id, self.mrp_workcenter_3.id) self.assertEqual(wo2.state, 'pending') self.assertEqual(wo3.state, 'pending') self.assertFalse(wo1.id in wo1._get_conflicted_workorder_ids(), "Shouldn't conflict") self.assertFalse(wo2.id in wo2._get_conflicted_workorder_ids(), "Shouldn't conflict") self.assertFalse(wo3.id in wo3._get_conflicted_workorder_ids(), "Shouldn't conflict") # Conflicted with wo1 wo2.write({'date_planned_start': wo1.date_planned_start, 'date_planned_finished': wo1.date_planned_finished}) # Bad order of workorders (wo3-wo1-wo2) + Late wo3.write({'date_planned_start': wo1.date_planned_start - timedelta(weeks=1), 'date_planned_finished': wo1.date_planned_finished - timedelta(weeks=1)}) self.assertTrue(wo2.id in wo2._get_conflicted_workorder_ids(), "Should conflict with wo1") self.assertTrue(wo1.id in wo1._get_conflicted_workorder_ids(), "Should conflict with wo2") self.assertTrue('text-danger' in wo2.json_popover, "Popover should in be in red (due to conflict)") self.assertTrue('text-danger' in wo3.json_popover, "Popover should in be in red (due to bad order of wo)") self.assertTrue('text-warning' in wo3.json_popover, "Popover contains of warning (late)") wo1.button_start() self.assertEqual(wo1.state, 'progress') self.assertEqual(wo2.id in wo2._get_conflicted_workorder_ids(), False, "Shouldn't have a conflict because wo1 is in progress") wo1_date_planned_start = wo1.date_planned_start wo2_date_planned_start = wo2.date_planned_start wo3_date_planned_start = wo3.date_planned_start wo2.action_replan() # Replan all MO of WO self.assertEqual(wo1.date_planned_start, wo1_date_planned_start, "Planned date of Workorder 1 shouldn't change (because it is in progress)") self.assertNotEqual(wo2.date_planned_start, wo2_date_planned_start, "Planned date of Workorder 2 should be updated") self.assertNotEqual(wo3.date_planned_start, wo3_date_planned_start, "Planned date of Workorder 3 should be updated") self.assertTrue(wo3.date_planned_start > wo2.date_planned_start, "Workorder 2 should be before the 3") class TestRoutingAndKits(TransactionCase): @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.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, 'consumption': 'flexible', 'product_qty': 1, 'type': 'normal', 'bom_line_ids': [ (0, 0, {'product_id': cls.compfinished1.id, 'product_qty': 1}), (0, 0, {'product_id': cls.kit1.id, 'product_qty': 1}), ], 'operation_ids': [ (0, 0, {'sequence': 1, 'name': 'finished operation 1', 'workcenter_id': cls.workcenter_finished1.id}), (0, 0, {'sequence': 2, 'name': 'finished operation 2', 'workcenter_id': cls.workcenter_finished1.id}), ], }) 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', 'bom_line_ids': [ (0, 0, {'product_id': cls.compkit1.id, 'product_qty': 1}), ], 'operation_ids': [ (0, 0, {'name': 'Kit operation', 'workcenter_id': cls.workcenter_kit1.id}) ] }) 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.bom_kit1.operation_ids[0] 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].move_raw_ids.move_line_ids), 0) self.assertEqual(mo.workorder_ids[1].move_raw_ids.product_id, self.compfinished1) self.assertEqual(mo.workorder_ids[2].move_raw_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].move_raw_ids.move_line_ids), 0) self.assertEqual(mo.workorder_ids[1].move_raw_ids.product_id, self.compfinished1) self.assertEqual(mo.workorder_ids[2].move_raw_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.bom_kit1.operation_ids self.bom_finished1.bom_line_ids[0].operation_id = self.bom_finished1.operation_ids[0] self.bom_finished1.bom_line_ids[1].operation_id = self.bom_finished1.operation_ids[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].move_raw_ids.product_id, self.compfinished1) self.assertFalse(mo.workorder_ids[1].move_raw_ids.product_id.id) self.assertEqual(mo.workorder_ids[2].move_raw_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].move_raw_ids), 0) self.assertEqual(set(mo.workorder_ids[1].move_raw_ids.product_id.ids), set([self.compfinished1.id, compkit2.id])) self.assertEqual(mo.workorder_ids[2].move_raw_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.bom_finished1.operation_ids[0] # 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.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].move_raw_ids.product_id, self.compfinished1) self.assertEqual(mo.workorder_ids[1].move_raw_ids.product_id, compkit2) self.assertEqual(mo.workorder_ids[2].move_raw_ids.product_id, self.compkit1) # ------------------------------------------------------------------------- # Those 2 next tests aren't related to routing and kit but to flexible # consumption. # ------------------------------------------------------------------------- def test_merge_lot(self): """ Produce 10 units of product tracked by lot on two workorder. On the first one, produce 4 onto lot1 then 6 onto lot1 as well. The second workorder should be prefilled with 10 units and lot1""" self.finished1.tracking = 'lot' lot1 = self.env['stock.lot'].create({ 'product_id': self.finished1.id, 'company_id': self.env.company.id, }) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.finished1 mo_form.bom_id = self.bom_finished1 mo_form.product_qty = 10.0 mo = mo_form.save() mo.action_confirm() mo.button_plan() wo1 = mo.workorder_ids.filtered(lambda wo: wo.state == 'waiting')[0] wo1.button_start() wo1.qty_producing = 4 wo1.finished_lot_id = lot1 wo1.record_production() backorder = mo.procurement_group_id.mrp_production_ids[-1] ba_wo1 = backorder.workorder_ids[0] self.assertEqual(ba_wo1.qty_producing, 6) self.assertEqual(ba_wo1.qty_produced, 0) self.assertEqual(ba_wo1.qty_remaining, 6) ba_wo1.finished_lot_id = lot1 ba_wo1.record_production() wo2 = mo.workorder_ids[2] wo2.button_start() ba_wo2 = backorder.workorder_ids[2] ba_wo2.button_start() self.assertEqual(wo2.qty_producing, 4) self.assertEqual(wo2.finished_lot_id, lot1) self.assertEqual(ba_wo2.qty_producing, 6) self.assertEqual(ba_wo2.finished_lot_id, lot1) def test_add_move(self): """ Make a production using multi step routing. Add an additional move on a specific operation and check that the produce is consumed into the right workorder. """ # Required for `product_uom` to be visible in the view self.env.user.groups_id += self.env.ref('uom.group_uom') self.bom_finished1.consumption = 'flexible' add_product = self.env['product.product'].create({ 'name': 'Additional', 'type': 'product', }) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.finished1 mo_form.bom_id = self.bom_finished1 mo_form.product_qty = 10.0 mo = mo_form.save() mo_form = Form(mo) with mo_form.move_raw_ids.new() as move: move.name = mo.name move.product_id = add_product move.product_uom = add_product.uom_id move.location_id = mo.location_src_id move.location_dest_id = mo.production_location_id move.product_uom_qty = 2 move.operation_id = mo.bom_id.operation_ids[0] mo = mo_form.save() self.assertEqual(len(mo.move_raw_ids), 3) mo.action_confirm() self.assertEqual(mo.move_raw_ids.mapped('state'), ['confirmed'] * 3) mo.button_plan() self.assertEqual(len(mo.workorder_ids), 3) wo1 = mo.workorder_ids[0] lines = wo1.move_raw_ids self.assertEqual(lines.product_id, add_product) def test_add_move_2(self): """ Make a production using multi step routing. Add an additional move on a specific operation and check that the produce is consumed into the right workorder. """ # Required for `product_uom` to be visible in the view self.env.user.groups_id += self.env.ref('uom.group_uom') self.bom_finished1.consumption = 'flexible' add_product = self.env['product.product'].create({ 'name': 'Additional', 'type': 'product', }) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.finished1 mo_form.bom_id = self.bom_finished1 mo_form.product_qty = 10.0 mo = mo_form.save() mo.action_confirm() mo.is_locked = False mo_form = Form(mo) with mo_form.move_raw_ids.new() as move: move.name = mo.name move.product_id = add_product move.product_uom = add_product.uom_id move.location_id = mo.location_src_id move.location_dest_id = mo.production_location_id move.product_uom_qty = 2 move.operation_id = mo.bom_id.operation_ids[0] mo = mo_form.save() new_move = mo.move_raw_ids.filtered(lambda move: move.product_id == add_product) self.assertEqual(len(mo.move_raw_ids), 3) self.assertEqual(len(new_move), 1) self.assertEqual(mo.move_raw_ids.mapped('state'), ['confirmed'] * 3) mo.button_plan() self.assertEqual(len(mo.workorder_ids), 3) wo1 = mo.workorder_ids[0] lines = wo1.move_raw_ids self.assertEqual(lines.product_id, add_product)