diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 64ed220b5b6..758cd1a2953 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -364,6 +364,7 @@ class MrpProduction(models.Model): 'location_dest_id': self.location_dest_id.id, 'company_id': self.company_id.id, 'production_id': self.id, + 'warehouse_id': self.location_dest_id.get_warehouse().id, 'origin': self.name, 'group_id': self.procurement_group_id.id, 'propagate': self.propagate, diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py index 0f892beeb90..a611cfa2031 100644 --- a/addons/mrp/models/mrp_unbuild.py +++ b/addons/mrp/models/mrp_unbuild.py @@ -157,6 +157,7 @@ class MrpUnbuild(models.Model): 'product_uom_qty': unbuild.product_qty, 'location_id': unbuild.location_id.id, 'location_dest_id': unbuild.product_id.property_stock_production.id, + 'warehouse_id': unbuild.location_id.get_warehouse().id, 'origin': unbuild.name, 'consume_unbuild_id': unbuild.id, }) @@ -189,6 +190,7 @@ class MrpUnbuild(models.Model): 'procure_method': 'make_to_stock', 'location_dest_id': self.location_dest_id.id, 'location_id': raw_move.location_dest_id.id, + 'warehouse_id': self.location_dest_id.get_warehouse().id, 'unbuild_id': self.id, }) @@ -203,6 +205,7 @@ class MrpUnbuild(models.Model): 'procure_method': 'make_to_stock', 'location_dest_id': self.location_dest_id.id, 'location_id': self.product_id.property_stock_production.id, + 'warehouse_id': self.location_dest_id.get_warehouse().id, 'unbuild_id': self.id, }) diff --git a/addons/mrp/tests/test_unbuild.py b/addons/mrp/tests/test_unbuild.py index 505e47c4b13..bb6b01cd67d 100644 --- a/addons/mrp/tests/test_unbuild.py +++ b/addons/mrp/tests/test_unbuild.py @@ -450,4 +450,127 @@ class TestUnbuild(TestMrpCommon): ml = mo.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and m.lot_produced_id == lot_finished_1) self.assertEqual(ml[0].qty_done, 12.0, 'Should have consumed 12 for the first lot') ml = mo.finished_move_line_ids[1].consume_line_ids.filtered(lambda m: m.product_id == p1 and m.lot_produced_id == lot_finished_2) - self.assertEqual(ml[0].qty_done, 8.0, 'Should have consumed 8 for the second lot') \ No newline at end of file + self.assertEqual(ml[0].qty_done, 8.0, 'Should have consumed 8 for the second lot') + + def test_unbuild_with_routes(self): + """ This test creates a MO of a stockable product (Table). A new route for rule QC/Unbuild -> Stock + is created with Warehouse -> True. + The unbuild order should revert the consumed components into QC/Unbuild location for quality check + and then a picking should be generated for transferring components from QC/Unbuild location to stock. + """ + StockQuant = self.env['stock.quant'] + ProductObj = self.env['product.product'] + # Create new QC/Unbuild location + warehouse = self.env.ref('stock.warehouse0') + unbuild_location = self.env['stock.location'].create({ + 'name': 'QC/Unbuild', + 'usage': 'internal', + 'location_id': warehouse.view_location_id.id + }) + + # Create a product route containing a stock rule that will move product from QC/Unbuild location to stock + product_route = self.env['stock.location.route'].create({ + 'name': 'QC/Unbuild -> Stock', + 'warehouse_selectable': True, + 'warehouse_ids': [(4, warehouse.id)], + 'rule_ids': [(0, 0, { + 'name': 'Send Matrial QC/Unbuild -> Stock', + 'action': 'push', + 'picking_type_id': self.ref('stock.picking_type_internal'), + 'location_src_id': unbuild_location.id, + 'location_id': self.stock_location.id, + })], + }) + + # Create a stockable product and its components + finshed_product = ProductObj.create({ + 'name': 'Table', + 'type': 'product', + }) + component1 = ProductObj.create({ + 'name': 'Table head', + 'type': 'product', + }) + component2 = ProductObj.create({ + 'name': 'Table stand', + 'type': 'product', + }) + + # Create bom and add components + bom = self.env['mrp.bom'].create({ + 'product_id': finshed_product.id, + 'product_tmpl_id': finshed_product.product_tmpl_id.id, + 'product_uom_id': self.uom_unit.id, + 'product_qty': 1.0, + 'type': 'normal', + 'bom_line_ids': [ + (0, 0, {'product_id': component1.id, 'product_qty': 1}), + (0, 0, {'product_id': component2.id, 'product_qty': 1}) + ]}) + + # Set on hand quantity + StockQuant._update_available_quantity(component1, self.stock_location, 1) + StockQuant._update_available_quantity(component2, self.stock_location, 1) + + # Create mo + mo = self.env['mrp.production'].create({ + 'name': 'MO 1', + 'product_id': finshed_product.id, + 'product_uom_id': finshed_product.uom_id.id, + 'product_qty': 1.0, + 'bom_id': bom.id, + }) + self.assertEqual(len(mo), 1, 'MO should have been created') + mo.action_assign() + + # Produce the final product + produce_form = Form(self.env['mrp.product.produce'].with_context({ + 'active_id': mo.id, + 'active_ids': [mo.id], + })) + produce_form.product_qty = 1.0 + produce_wizard = produce_form.save() + produce_wizard.do_produce() + + mo.button_mark_done() + self.assertEqual(mo.state, 'done', "Production order should be in done state.") + + # Check quantity in stock before unbuild + self.assertEqual(StockQuant._get_available_quantity(finshed_product, self.stock_location), 1, 'Table should be available in stock') + self.assertEqual(StockQuant._get_available_quantity(component1, self.stock_location), 0, 'Table head should not be available in stock') + self.assertEqual(StockQuant._get_available_quantity(component2, self.stock_location), 0, 'Table stand should not be available in stock') + + # --------------------------------------------------- + # Unbuild + # --------------------------------------------------- + + # Create an unbuild order of the finished product and set the destination loacation = QC/Unbuild + self.env['mrp.unbuild'].create({ + 'product_id': finshed_product.id, + 'bom_id': bom.id, + 'product_qty': 1.0, + 'product_uom_id': self.uom_unit.id, + 'mo_id': mo.id, + 'location_id': self.stock_location.id, + 'location_dest_id': unbuild_location.id + }).action_unbuild() + + # Check the available quantity of components and final product in stock + self.assertEqual(StockQuant._get_available_quantity(finshed_product, self.stock_location), 0, 'Table should not be available in stock as it is unbuild') + self.assertEqual(StockQuant._get_available_quantity(component1, self.stock_location), 0, 'Table head should not be available in stock as it is in QC/Unbuild location') + self.assertEqual(StockQuant._get_available_quantity(component2, self.stock_location), 0, 'Table stand should not be available in stock as it is in QC/Unbuild location') + + # Find new generated picking + picking = self.env['stock.picking'].search([('product_id', 'in', [component1.id, component2.id])]) + self.assertEqual(picking.location_id.id, unbuild_location.id, 'Wrong source location in picking') + self.assertEqual(picking.location_dest_id.id, self.stock_location.id, 'Wrong destination location in picking') + + # Transfer it + for ml in picking.move_ids_without_package: + ml.quantity_done = 1 + picking.action_done() + + # Check the available quantity of components and final product in stock + self.assertEqual(StockQuant._get_available_quantity(finshed_product, self.stock_location), 0, 'Table should not be available in stock') + self.assertEqual(StockQuant._get_available_quantity(component1, self.stock_location), 1, 'Table head should be available in stock as the picking is transferred') + self.assertEqual(StockQuant._get_available_quantity(component2, self.stock_location), 1, 'Table stand should be available in stock as the picking is transferred') diff --git a/addons/mrp_byproduct/models/mrp_production.py b/addons/mrp_byproduct/models/mrp_production.py index 05584d0d019..e89bdd855a9 100644 --- a/addons/mrp_byproduct/models/mrp_production.py +++ b/addons/mrp_byproduct/models/mrp_production.py @@ -27,6 +27,7 @@ class MrpProduction(models.Model): 'location_dest_id': production.location_dest_id.id, 'operation_id': sub_product.operation_id.id, 'production_id': production.id, + 'warehouse_id': production.location_dest_id.get_warehouse().id, 'origin': production.name, 'unit_factor': qty1 / (production.product_qty - production.qty_produced), 'subproduct_id': sub_product.id