1# -*- coding: utf-8 -*- 2# Part of Odoo. See LICENSE file for full copyright and licensing details. 3 4from odoo.tests import Form 5from odoo.addons.mrp.tests.common import TestMrpCommon 6 7 8class TestMultistepManufacturingWarehouse(TestMrpCommon): 9 10 def setUp(self): 11 super(TestMultistepManufacturingWarehouse, self).setUp() 12 # Create warehouse 13 self.customer_location = self.env['ir.model.data'].xmlid_to_res_id('stock.stock_location_customers') 14 warehouse_form = Form(self.env['stock.warehouse']) 15 warehouse_form.name = 'Test Warehouse' 16 warehouse_form.code = 'TWH' 17 self.warehouse = warehouse_form.save() 18 19 self.uom_unit = self.env.ref('uom.product_uom_unit') 20 21 # Create manufactured product 22 product_form = Form(self.env['product.product']) 23 product_form.name = 'Stick' 24 product_form.uom_id = self.uom_unit 25 product_form.uom_po_id = self.uom_unit 26 product_form.type = 'product' 27 product_form.route_ids.clear() 28 product_form.route_ids.add(self.warehouse.manufacture_pull_id.route_id) 29 product_form.route_ids.add(self.warehouse.mto_pull_id.route_id) 30 self.finished_product = product_form.save() 31 32 # Create raw product for manufactured product 33 product_form = Form(self.env['product.product']) 34 product_form.name = 'Raw Stick' 35 product_form.type = 'product' 36 product_form.uom_id = self.uom_unit 37 product_form.uom_po_id = self.uom_unit 38 self.raw_product = product_form.save() 39 40 # Create bom for manufactured product 41 bom_product_form = Form(self.env['mrp.bom']) 42 bom_product_form.product_id = self.finished_product 43 bom_product_form.product_tmpl_id = self.finished_product.product_tmpl_id 44 bom_product_form.product_qty = 1.0 45 bom_product_form.type = 'normal' 46 with bom_product_form.bom_line_ids.new() as bom_line: 47 bom_line.product_id = self.raw_product 48 bom_line.product_qty = 2.0 49 50 self.bom = bom_product_form.save() 51 52 def _check_location_and_routes(self): 53 # Check manufacturing pull rule. 54 self.assertTrue(self.warehouse.manufacture_pull_id) 55 self.assertTrue(self.warehouse.manufacture_pull_id.active, self.warehouse.manufacture_to_resupply) 56 self.assertTrue(self.warehouse.manufacture_pull_id.route_id) 57 # Check new routes created or not. 58 self.assertTrue(self.warehouse.pbm_route_id) 59 # Check location should be created and linked to warehouse. 60 self.assertTrue(self.warehouse.pbm_loc_id) 61 self.assertEqual(self.warehouse.pbm_loc_id.active, self.warehouse.manufacture_steps != 'mrp_one_step', "Input location must be de-active for single step only.") 62 self.assertTrue(self.warehouse.manu_type_id.active) 63 64 def test_00_create_warehouse(self): 65 """ Warehouse testing for direct manufacturing """ 66 with Form(self.warehouse) as warehouse: 67 warehouse.manufacture_steps = 'mrp_one_step' 68 self._check_location_and_routes() 69 # Check locations of existing pull rule 70 self.assertFalse(self.warehouse.pbm_route_id.rule_ids, 'only the update of global manufacture route should happen.') 71 self.assertEqual(self.warehouse.manufacture_pull_id.location_id.id, self.warehouse.lot_stock_id.id) 72 73 def test_01_warehouse_twostep_manufacturing(self): 74 """ Warehouse testing for picking before manufacturing """ 75 with Form(self.warehouse) as warehouse: 76 warehouse.manufacture_steps = 'pbm' 77 self._check_location_and_routes() 78 self.assertEqual(len(self.warehouse.pbm_route_id.rule_ids), 2) 79 self.assertEqual(self.warehouse.manufacture_pull_id.location_id.id, self.warehouse.lot_stock_id.id) 80 81 def test_02_warehouse_twostep_manufacturing(self): 82 """ Warehouse testing for picking ans store after manufacturing """ 83 with Form(self.warehouse) as warehouse: 84 warehouse.manufacture_steps = 'pbm_sam' 85 self._check_location_and_routes() 86 self.assertEqual(len(self.warehouse.pbm_route_id.rule_ids), 3) 87 self.assertEqual(self.warehouse.manufacture_pull_id.location_id.id, self.warehouse.sam_loc_id.id) 88 89 def test_manufacturing_3_steps(self): 90 """ Test MO/picking before manufacturing/picking after manufacturing 91 components and move_orig/move_dest. Ensure that everything is created 92 correctly. 93 """ 94 with Form(self.warehouse) as warehouse: 95 warehouse.manufacture_steps = 'pbm_sam' 96 97 production_form = Form(self.env['mrp.production']) 98 production_form.product_id = self.finished_product 99 production_form.picking_type_id = self.warehouse.manu_type_id 100 production = production_form.save() 101 production.action_confirm() 102 103 move_raw_ids = production.move_raw_ids 104 self.assertEqual(len(move_raw_ids), 1) 105 self.assertEqual(move_raw_ids.product_id, self.raw_product) 106 self.assertEqual(move_raw_ids.picking_type_id, self.warehouse.manu_type_id) 107 pbm_move = move_raw_ids.move_orig_ids 108 self.assertEqual(len(pbm_move), 1) 109 self.assertEqual(pbm_move.location_id, self.warehouse.lot_stock_id) 110 self.assertEqual(pbm_move.location_dest_id, self.warehouse.pbm_loc_id) 111 self.assertEqual(pbm_move.picking_type_id, self.warehouse.pbm_type_id) 112 self.assertFalse(pbm_move.move_orig_ids) 113 114 move_finished_ids = production.move_finished_ids 115 self.assertEqual(len(move_finished_ids), 1) 116 self.assertEqual(move_finished_ids.product_id, self.finished_product) 117 self.assertEqual(move_finished_ids.picking_type_id, self.warehouse.manu_type_id) 118 sam_move = move_finished_ids.move_dest_ids 119 self.assertEqual(len(sam_move), 1) 120 self.assertEqual(sam_move.location_id, self.warehouse.sam_loc_id) 121 self.assertEqual(sam_move.location_dest_id, self.warehouse.lot_stock_id) 122 self.assertEqual(sam_move.picking_type_id, self.warehouse.sam_type_id) 123 self.assertFalse(sam_move.move_dest_ids) 124 125 def test_manufacturing_flow(self): 126 """ Simulate a pick pack ship delivery combined with a picking before 127 manufacturing and store after manufacturing. Also ensure that the MO and 128 the moves to stock are created with the generic pull rules. 129 In order to trigger the rule we create a picking to the customer with 130 the 'make to order' procure method 131 """ 132 with Form(self.warehouse) as warehouse: 133 warehouse.manufacture_steps = 'pbm_sam' 134 warehouse.delivery_steps = 'pick_pack_ship' 135 self.warehouse.flush() 136 self.env.ref('stock.route_warehouse0_mto').active = True 137 self.env['stock.quant']._update_available_quantity(self.raw_product, self.warehouse.lot_stock_id, 4.0) 138 picking_customer = self.env['stock.picking'].create({ 139 'location_id': self.warehouse.wh_output_stock_loc_id.id, 140 'location_dest_id': self.customer_location, 141 'partner_id': self.env['ir.model.data'].xmlid_to_res_id('base.res_partner_4'), 142 'picking_type_id': self.warehouse.out_type_id.id, 143 }) 144 self.env['stock.move'].create({ 145 'name': self.finished_product.name, 146 'product_id': self.finished_product.id, 147 'product_uom_qty': 2, 148 'product_uom': self.uom_unit.id, 149 'picking_id': picking_customer.id, 150 'location_id': self.warehouse.wh_output_stock_loc_id.id, 151 'location_dest_id': self.customer_location, 152 'procure_method': 'make_to_order', 153 'origin': 'SOURCEDOCUMENT', 154 'state': 'draft', 155 }) 156 picking_customer.action_confirm() 157 production_order = self.env['mrp.production'].search([('product_id', '=', self.finished_product.id)]) 158 self.assertTrue(production_order) 159 self.assertEqual(production_order.origin, 'SOURCEDOCUMENT', 'The MO origin should be the SO name') 160 self.assertNotEqual(production_order.name, 'SOURCEDOCUMENT', 'The MO name should not be the origin of the move') 161 162 picking_stock_preprod = self.env['stock.move'].search([ 163 ('product_id', '=', self.raw_product.id), 164 ('location_id', '=', self.warehouse.lot_stock_id.id), 165 ('location_dest_id', '=', self.warehouse.pbm_loc_id.id), 166 ('picking_type_id', '=', self.warehouse.pbm_type_id.id) 167 ]).picking_id 168 picking_stock_postprod = self.env['stock.move'].search([ 169 ('product_id', '=', self.finished_product.id), 170 ('location_id', '=', self.warehouse.sam_loc_id.id), 171 ('location_dest_id', '=', self.warehouse.lot_stock_id.id), 172 ('picking_type_id', '=', self.warehouse.sam_type_id.id) 173 ]).picking_id 174 175 self.assertTrue(picking_stock_preprod) 176 self.assertTrue(picking_stock_postprod) 177 self.assertEqual(picking_stock_preprod.state, 'confirmed') 178 self.assertEqual(picking_stock_postprod.state, 'waiting') 179 self.assertEqual(picking_stock_preprod.origin, production_order.name, 'The pre-prod origin should be the MO name') 180 self.assertEqual(picking_stock_postprod.origin, 'SOURCEDOCUMENT', 'The post-prod origin should be the SO name') 181 182 picking_stock_preprod.action_assign() 183 picking_stock_preprod.move_line_ids.qty_done = 4 184 picking_stock_preprod._action_done() 185 186 self.assertFalse(sum(self.env['stock.quant']._gather(self.raw_product, self.warehouse.lot_stock_id).mapped('quantity'))) 187 self.assertTrue(self.env['stock.quant']._gather(self.raw_product, self.warehouse.pbm_loc_id)) 188 189 production_order.action_assign() 190 self.assertEqual(production_order.reservation_state, 'assigned') 191 self.assertEqual(picking_stock_postprod.state, 'waiting') 192 193 produce_form = Form(production_order) 194 produce_form.qty_producing = production_order.product_qty 195 production_order = produce_form.save() 196 production_order.button_mark_done() 197 198 self.assertFalse(sum(self.env['stock.quant']._gather(self.raw_product, self.warehouse.pbm_loc_id).mapped('quantity'))) 199 200 self.assertEqual(picking_stock_postprod.state, 'assigned') 201 202 picking_stock_pick = self.env['stock.move'].search([ 203 ('product_id', '=', self.finished_product.id), 204 ('location_id', '=', self.warehouse.lot_stock_id.id), 205 ('location_dest_id', '=', self.warehouse.wh_pack_stock_loc_id.id), 206 ('picking_type_id', '=', self.warehouse.pick_type_id.id) 207 ]).picking_id 208 self.assertEqual(picking_stock_pick.move_lines.move_orig_ids.picking_id, picking_stock_postprod) 209 210 def test_cancel_propagation(self): 211 """ Test cancelling moves in a 'picking before 212 manufacturing' and 'store after manufacturing' process. The propagation of 213 cancel depends on the default values on each rule of the chain. 214 """ 215 self.warehouse.manufacture_steps = 'pbm_sam' 216 self.warehouse.flush() 217 self.env['stock.quant']._update_available_quantity(self.raw_product, self.warehouse.lot_stock_id, 4.0) 218 picking_customer = self.env['stock.picking'].create({ 219 'location_id': self.warehouse.lot_stock_id.id, 220 'location_dest_id': self.customer_location, 221 'partner_id': self.env['ir.model.data'].xmlid_to_res_id('base.res_partner_4'), 222 'picking_type_id': self.warehouse.out_type_id.id, 223 }) 224 self.env['stock.move'].create({ 225 'name': self.finished_product.name, 226 'product_id': self.finished_product.id, 227 'product_uom_qty': 2, 228 'picking_id': picking_customer.id, 229 'product_uom': self.uom_unit.id, 230 'location_id': self.warehouse.lot_stock_id.id, 231 'location_dest_id': self.customer_location, 232 'procure_method': 'make_to_order', 233 }) 234 picking_customer.action_confirm() 235 production_order = self.env['mrp.production'].search([('product_id', '=', self.finished_product.id)]) 236 self.assertTrue(production_order) 237 238 move_stock_preprod = self.env['stock.move'].search([ 239 ('product_id', '=', self.raw_product.id), 240 ('location_id', '=', self.warehouse.lot_stock_id.id), 241 ('location_dest_id', '=', self.warehouse.pbm_loc_id.id), 242 ('picking_type_id', '=', self.warehouse.pbm_type_id.id) 243 ]) 244 move_stock_postprod = self.env['stock.move'].search([ 245 ('product_id', '=', self.finished_product.id), 246 ('location_id', '=', self.warehouse.sam_loc_id.id), 247 ('location_dest_id', '=', self.warehouse.lot_stock_id.id), 248 ('picking_type_id', '=', self.warehouse.sam_type_id.id) 249 ]) 250 251 self.assertTrue(move_stock_preprod) 252 self.assertTrue(move_stock_postprod) 253 self.assertEqual(move_stock_preprod.state, 'confirmed') 254 self.assertEqual(move_stock_postprod.state, 'waiting') 255 256 move_stock_preprod._action_cancel() 257 self.assertEqual(production_order.state, 'confirmed') 258 production_order.action_cancel() 259 self.assertTrue(move_stock_postprod.state, 'cancel') 260 261 def test_no_initial_demand(self): 262 """ Test MO/picking before manufacturing/picking after manufacturing 263 components and move_orig/move_dest. Ensure that everything is created 264 correctly. 265 """ 266 with Form(self.warehouse) as warehouse: 267 warehouse.manufacture_steps = 'pbm_sam' 268 production_form = Form(self.env['mrp.production']) 269 production_form.product_id = self.finished_product 270 production_form.picking_type_id = self.warehouse.manu_type_id 271 production = production_form.save() 272 production.move_raw_ids.product_uom_qty = 0 273 production.action_confirm() 274 production.action_assign() 275 self.assertFalse(production.move_raw_ids.move_orig_ids) 276 self.assertEqual(production.state, 'confirmed') 277 self.assertEqual(production.reservation_state, 'assigned') 278 279 def test_manufacturing_3_steps_flexible(self): 280 """ Test MO/picking before manufacturing/picking after manufacturing 281 components and move_orig/move_dest. Ensure that additional moves are put 282 in picking before manufacturing too. 283 """ 284 with Form(self.warehouse) as warehouse: 285 warehouse.manufacture_steps = 'pbm_sam' 286 bom = self.env['mrp.bom'].search([ 287 ('product_id', '=', self.finished_product.id) 288 ]) 289 new_product = self.env['product.product'].create({ 290 'name': 'New product', 291 'type': 'product', 292 }) 293 bom.consumption = 'flexible' 294 production_form = Form(self.env['mrp.production']) 295 production_form.product_id = self.finished_product 296 production_form.picking_type_id = self.warehouse.manu_type_id 297 production = production_form.save() 298 299 production.action_confirm() 300 301 production_form = Form(production) 302 with production_form.move_raw_ids.new() as move: 303 move.product_id = new_product 304 move.product_uom_qty = 2 305 production = production_form.save() 306 move_raw_ids = production.move_raw_ids 307 self.assertEqual(len(move_raw_ids), 2) 308 pbm_move = move_raw_ids.move_orig_ids 309 self.assertEqual(len(pbm_move), 2) 310 self.assertTrue(new_product in pbm_move.product_id) 311 312 def test_manufacturing_complex_product_3_steps(self): 313 """ Test MO/picking after manufacturing a complex product which uses 314 manufactured components. Ensure that everything is created and picked 315 correctly. 316 """ 317 318 self.warehouse.mto_pull_id.route_id.active = True 319 # Creating complex product which trigger another manifacture 320 321 product_form = Form(self.env['product.product']) 322 product_form.name = 'Arrow' 323 product_form.type = 'product' 324 product_form.route_ids.clear() 325 product_form.route_ids.add(self.warehouse.manufacture_pull_id.route_id) 326 product_form.route_ids.add(self.warehouse.mto_pull_id.route_id) 327 self.complex_product = product_form.save() 328 329 ## Create raw product for manufactured product 330 product_form = Form(self.env['product.product']) 331 product_form.name = 'Raw Iron' 332 product_form.type = 'product' 333 product_form.uom_id = self.uom_unit 334 product_form.uom_po_id = self.uom_unit 335 self.raw_product_2 = product_form.save() 336 337 with Form(self.finished_product) as finished_product: 338 finished_product.route_ids.clear() 339 finished_product.route_ids.add(self.warehouse.manufacture_pull_id.route_id) 340 finished_product.route_ids.add(self.warehouse.mto_pull_id.route_id) 341 342 ## Create bom for manufactured product 343 bom_product_form = Form(self.env['mrp.bom']) 344 bom_product_form.product_id = self.complex_product 345 bom_product_form.product_tmpl_id = self.complex_product.product_tmpl_id 346 with bom_product_form.bom_line_ids.new() as line: 347 line.product_id = self.finished_product 348 line.product_qty = 1.0 349 with bom_product_form.bom_line_ids.new() as line: 350 line.product_id = self.raw_product_2 351 line.product_qty = 1.0 352 353 self.complex_bom = bom_product_form.save() 354 355 with Form(self.warehouse) as warehouse: 356 warehouse.manufacture_steps = 'pbm_sam' 357 358 production_form = Form(self.env['mrp.production']) 359 production_form.product_id = self.complex_product 360 production_form.picking_type_id = self.warehouse.manu_type_id 361 production = production_form.save() 362 production.action_confirm() 363 364 move_raw_ids = production.move_raw_ids 365 self.assertEqual(len(move_raw_ids), 2) 366 sfp_move_raw_id, raw_move_raw_id = move_raw_ids 367 self.assertEqual(sfp_move_raw_id.product_id, self.finished_product) 368 self.assertEqual(raw_move_raw_id.product_id, self.raw_product_2) 369 370 for move_raw_id in move_raw_ids: 371 self.assertEqual(move_raw_id.picking_type_id, self.warehouse.manu_type_id) 372 373 pbm_move = move_raw_id.move_orig_ids 374 self.assertEqual(len(pbm_move), 1) 375 self.assertEqual(pbm_move.location_id, self.warehouse.lot_stock_id) 376 self.assertEqual(pbm_move.location_dest_id, self.warehouse.pbm_loc_id) 377 self.assertEqual(pbm_move.picking_type_id, self.warehouse.pbm_type_id) 378 379 # Check move locations 380 move_finished_ids = production.move_finished_ids 381 self.assertEqual(len(move_finished_ids), 1) 382 self.assertEqual(move_finished_ids.product_id, self.complex_product) 383 self.assertEqual(move_finished_ids.picking_type_id, self.warehouse.manu_type_id) 384 sam_move = move_finished_ids.move_dest_ids 385 self.assertEqual(len(sam_move), 1) 386 self.assertEqual(sam_move.location_id, self.warehouse.sam_loc_id) 387 self.assertEqual(sam_move.location_dest_id, self.warehouse.lot_stock_id) 388 self.assertEqual(sam_move.picking_type_id, self.warehouse.sam_type_id) 389 self.assertFalse(sam_move.move_dest_ids) 390 391 subproduction = self.env['mrp.production'].browse(production.id+1) 392 sfp_pickings = subproduction.picking_ids.sorted('id') 393 394 # SFP Production: 2 pickings, 1 group 395 self.assertEqual(len(sfp_pickings), 2) 396 self.assertEqual(sfp_pickings.mapped('group_id'), subproduction.procurement_group_id) 397 398 ## Move Raw Stick - Stock -> Preprocessing 399 picking = sfp_pickings[0] 400 self.assertEqual(len(picking.move_lines), 1) 401 picking.move_lines[0].product_id = self.raw_product 402 403 ## Move SFP - PostProcessing -> Stock 404 picking = sfp_pickings[1] 405 self.assertEqual(len(picking.move_lines), 1) 406 picking.move_lines[0].product_id = self.finished_product 407 408 # Main production 2 pickings, 1 group 409 pickings = production.picking_ids.sorted('id') 410 self.assertEqual(len(pickings), 2) 411 self.assertEqual(pickings.mapped('group_id'), production.procurement_group_id) 412 413 ## Move 2 components Stock -> Preprocessing 414 picking = pickings[0] 415 self.assertEqual(len(picking.move_lines), 2) 416 picking.move_lines[0].product_id = self.finished_product 417 picking.move_lines[1].product_id = self.raw_product_2 418 419 ## Move FP PostProcessing -> Stock 420 picking = pickings[1] 421 self.assertEqual(len(picking.move_lines), 1) 422 picking.product_id = self.complex_product 423 424 def test_3_steps_and_byproduct(self): 425 """ Suppose a warehouse with Manufacture option set to '3 setps' and a product P01 with a reordering rule. 426 Suppose P01 has a BoM and this BoM mentions that when some P01 are produced, some P02 are produced too. 427 This test ensures that when a MO is generated thanks to the reordering rule, 2 pickings are also 428 generated: 429 - One to bring the components 430 - Another to return the P01 and P02 produced 431 """ 432 warehouse = self.warehouse 433 warehouse.manufacture_steps = 'pbm_sam' 434 warehouse_stock_location = warehouse.lot_stock_id 435 pre_production_location = warehouse.pbm_loc_id 436 post_production_location = warehouse.sam_loc_id 437 438 one_unit_uom = self.env['ir.model.data'].xmlid_to_object('uom.product_uom_unit') 439 [two_units_uom, four_units_uom] = self.env['uom.uom'].create([{ 440 'name': 'x%s' % i, 441 'category_id': self.ref('uom.product_uom_categ_unit'), 442 'uom_type': 'bigger', 443 'factor_inv': i, 444 } for i in [2, 4]]) 445 446 finished_product = self.env['product.product'].create({ 447 'name': 'Super Product', 448 'route_ids': [(4, self.ref('mrp.route_warehouse0_manufacture'))], 449 'type': 'product', 450 }) 451 secondary_product = self.env['product.product'].create({ 452 'name': 'Secondary', 453 'type': 'product', 454 }) 455 component = self.env['product.product'].create({ 456 'name': 'Component', 457 'type': 'consu', 458 }) 459 460 bom = self.env['mrp.bom'].create({ 461 'product_tmpl_id': finished_product.product_tmpl_id.id, 462 'product_qty': 1, 463 'product_uom_id': two_units_uom.id, 464 'bom_line_ids': [(0, 0, { 465 'product_id': component.id, 466 'product_qty': 1, 467 'product_uom_id': one_unit_uom.id, 468 })], 469 'byproduct_ids': [(0, 0, { 470 'product_id': secondary_product.id, 471 'product_qty': 1, 472 'product_uom_id': four_units_uom.id, 473 })], 474 }) 475 476 orderpoint = self.env['stock.warehouse.orderpoint'].create({ 477 'warehouse_id': warehouse.id, 478 'location_id': warehouse_stock_location.id, 479 'product_id': finished_product.id, 480 'product_min_qty': 2, 481 'product_max_qty': 2, 482 }) 483 484 self.env['procurement.group'].run_scheduler() 485 mo = self.env['mrp.production'].search([('product_id', '=', finished_product.id)]) 486 pickings = mo.picking_ids 487 self.assertEqual(len(pickings), 2) 488 489 preprod_picking = pickings[0] if pickings[0].location_id == warehouse_stock_location else pickings[1] 490 self.assertEqual(preprod_picking.location_id, warehouse_stock_location) 491 self.assertEqual(preprod_picking.location_dest_id, pre_production_location) 492 493 postprod_picking = pickings - preprod_picking 494 self.assertEqual(postprod_picking.location_id, post_production_location) 495 self.assertEqual(postprod_picking.location_dest_id, warehouse_stock_location) 496 497 postprod_SML = postprod_picking.move_lines 498 self.assertEqual(len(postprod_SML), 2) 499 self.assertEqual(postprod_SML.location_id, post_production_location) 500 self.assertEqual(postprod_SML.location_dest_id, warehouse_stock_location) 501 502 finished_product_SML = postprod_SML[0] if postprod_SML[0].product_id == finished_product else postprod_SML[1] 503 secondary_product_SML = postprod_SML - finished_product_SML 504 self.assertEqual(finished_product_SML.product_uom.id, one_unit_uom.id) 505 self.assertEqual(finished_product_SML.product_uom_qty, 2) 506 self.assertEqual(secondary_product_SML.product_uom.id, four_units_uom.id) 507 self.assertEqual(secondary_product_SML.product_uom_qty, 1) 508