From 3e8a45fce0af0aa5792f0aea978d4ab78ab50a40 Mon Sep 17 00:00:00 2001 From: Adrien Widart Date: Fri, 29 Jul 2022 10:25:10 +0000 Subject: [PATCH] [FIX] stock,sale_mrp: avoid post-process scheduler if no procurement When the post-production location is a child of the stock location, running the scheduler may lead to a recursion error. To reproduce the issue: (Enable debug mode. Use demo data) 1. In Settings, enable "Multi-Step Routes" 2. Edit the warehouse: - Manufacture: 3 steps 3. In the locations, edit WH/Post-Production: - Parent Location: WH/Stock 4. Create two products P_finished, P_compo: - P_finished: - Storable - Route Manufacture 5. Create a reordering rule RR for P_finished: - Location: WH/Stock - Min: 0 - Max: 0 6. Create a BoM: - Product: P_finished - Type: Manufacture - Components: 1 x P_compo 7. Create a sale order SO with 1 x P_finished 8. Confirm SO Error: an Odoo Server Error is displayed: "RecursionError" When confirming the SO, it leads to the creation and the confirmation of the delivery. When confirming the delivery, we trigger the scheduler: because of the delivery, there is a need in WH/Stock. Moreover, there is an reordering rule RR for that product and that location. Therefore, we create and process a procurement and this will create the manufacturing order for P_finished. Then, the post-process of the scheduler is called: 1. We find the draft MO related to the RR and confirm it: https://github.com/odoo/odoo/blob/eabf5cc14b4c731f112e6c78b579ef8e98edcaad/addons/mrp/models/stock_orderpoint.py#L121-L125 2. When confirming the MO, we first try to confirm the associated pickings (from Pre-Prod to Stock and from Post-Prod to Stock). Note that we will update the state of the MO after the pickings confirmation: https://github.com/odoo/odoo/blob/14431b2497dcf4aaf778ef1b06e023101b00a4e2/addons/mrp/models/mrp_production.py#L1204-L1208 3. When confirming a picking, we trigger the scheduler: https://github.com/odoo/odoo/blob/fcc74186330c8df37cfac08455bd7bba44d4656b/addons/stock/models/stock_picking.py#L790-L791 4. We have two pickings, each one with one stock move: the first one with 1 x P_compo from Pre-Prod to Stock, and a second one with 1 x P_finished from Post-Prod to Stock. When triggering the scheduler, we try to find a RR that fulfill the need of each SM: https://github.com/odoo/odoo/blob/0183298293192538a801f52262c047ea34a1b76a/addons/stock/models/stock_move.py#L1949-L1954 The location of the created reordering rule is WH/Stock. The source location of the second stock move is WH/Stock/Post-Production, i.e. a child of WH/Stock. Therefore, we find the reordering rule we created and we try to process an associated procurement: https://github.com/odoo/odoo/blob/0183298293192538a801f52262c047ea34a1b76a/addons/stock/models/stock_move.py#L1961-L1962 5. Because there isn't any quantity to order, we don't create and process any procurement: https://github.com/odoo/odoo/blob/029da238a2baaf59bdf9ae828a4739ee4a9f3ede/addons/stock/models/stock_orderpoint.py#L519-L525 However, we still call the post-process of the scheduler: https://github.com/odoo/odoo/blob/029da238a2baaf59bdf9ae828a4739ee4a9f3ede/addons/stock/models/stock_orderpoint.py#L548-L549 This brings us back to the step 1 => Recursion ! OPW-2926657 closes odoo/odoo#97646 X-original-commit: c06e1ec755206e8dd8aff6b341151848e6d173c7 Signed-off-by: William Henrotin (whe) Signed-off-by: Adrien Widart --- .../tests/test_sale_mrp_procurement.py | 58 +++++++++++++++++++ addons/stock/models/stock_move.py | 3 +- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/addons/sale_mrp/tests/test_sale_mrp_procurement.py b/addons/sale_mrp/tests/test_sale_mrp_procurement.py index 00c064b5136..37b06937939 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_procurement.py +++ b/addons/sale_mrp/tests/test_sale_mrp_procurement.py @@ -181,3 +181,61 @@ class TestSaleMrpProcurement(TransactionCase): # ...with two products self.assertEqual(len(pickings[0].move_ids), 2) + + def test_post_prod_location_child_of_stock_location(self): + """ + 3-steps manufacturing, the post-prod location is a child of the stock + location. Have a manufactured product with the manufacture route and a + RR min=max=0. Confirm a SO with that product -> It should generate a MO + """ + warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) + manufacture_route = warehouse.manufacture_pull_id.route_id + + warehouse.manufacture_steps = 'pbm_sam' + warehouse.sam_loc_id.location_id = warehouse.lot_stock_id + + product, component = self.env['product.product'].create([{ + 'name': 'Finished', + 'type': 'product', + 'route_ids': [(6, 0, manufacture_route.ids)], + }, { + 'name': 'Component', + 'type': 'consu', + }]) + + self.env['mrp.bom'].create({ + 'product_id': product.id, + 'product_tmpl_id': product.product_tmpl_id.id, + 'product_uom_id': product.uom_id.id, + 'product_qty': 1.0, + 'type': 'normal', + 'bom_line_ids': [ + (0, 0, {'product_id': component.id, 'product_qty': 1}), + ], + }) + + self.env['stock.warehouse.orderpoint'].create({ + 'name': product.name, + 'location_id': warehouse.lot_stock_id.id, + 'product_id': product.id, + 'product_min_qty': 0, + 'product_max_qty': 0, + 'trigger': 'auto', + }) + + so = self.env['sale.order'].create({ + 'partner_id': self.env['res.partner'].create({'name': 'Super Partner'}).id, + 'order_line': [ + (0, 0, { + 'name': product.name, + 'product_id': product.id, + 'product_uom_qty': 1.0, + 'product_uom': product.uom_id.id, + 'price_unit': 1, + })], + }) + so.action_confirm() + self.assertEqual(so.state, 'sale') + + mo = self.env['mrp.production'].search([('product_id', '=', product.id)], order='id desc', limit=1) + self.assertIn(so.name, mo.origin) diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 17466531dca..46d914694ee 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -1977,7 +1977,8 @@ class StockMove(models.Model): ('product_id', '=', move.product_id.id), ('trigger', '=', 'auto'), ('location_id', 'parent_of', move.location_id.id), - ('company_id', '=', move.company_id.id) + ('company_id', '=', move.company_id.id), + '!', ('location_id', 'parent_of', move.location_dest_id.id), ], limit=1) if orderpoint: orderpoints_by_company[orderpoint.company_id] |= orderpoint