From 9d6f200af1bbbd8d8975ce6b5d26f2f96478fe1f Mon Sep 17 00:00:00 2001 From: "lase@odoo.com" Date: Wed, 11 Sep 2024 07:00:46 +0000 Subject: [PATCH] [FIX] stock: consider multi-steps in forecast availability Steps to reproduce: - Enable multi-step routes in the settings - Inventory > Configuration > Warehouse Management > Warehouses - Change your warehouse to manufacturing in 3 steps - Create a storable product COMP with 10 units in stock - Create a manufacturing order for a product Final product using COMP as components > The forcast of the components indicates that they are not available Cause of the issue: The location query of the `_get_forecast_availability_outgoing` was recently changed to: https://github.com/odoo/odoo/blob/49061347c181b1a451435bc363bb9508db8b6fab/addons/stock/models/stock_move.py#L2253 This change was made for optimisation purposes because the forecast lines now take care of the precise locations here: https://github.com/odoo/odoo/blob/49061347c181b1a451435bc363bb9508db8b6fab/addons/stock/models/stock_move.py#L2255 However, this optimisation is not correct as the forecast the result of this query is used in order to determine if a move is incoming or outgoing here: https://github.com/odoo/odoo/blob/49061347c181b1a451435bc363bb9508db8b6fab/addons/stock/report/stock_forecasted.py#L31-L44 and these domains only makes sense if the wh_location_ids are indeed all the locations of the warehouse and not these that are children of the "pre-prod" location. opw-3979953 closes odoo/odoo#179989 X-original-commit: be55bf02f205ec802fb386732570740e886b66e7 Signed-off-by: William Henrotin (whe) Signed-off-by: Lancelot Semal (lase) --- addons/mrp/tests/test_stock_report.py | 33 +++++++++++++++++++++++++++ addons/stock/models/stock_move.py | 3 +-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/addons/mrp/tests/test_stock_report.py b/addons/mrp/tests/test_stock_report.py index b15495d1bae75..a99c6d04961e8 100644 --- a/addons/mrp/tests/test_stock_report.py +++ b/addons/mrp/tests/test_stock_report.py @@ -387,3 +387,36 @@ def test_overview_with_component_also_as_byproduct(self): mo.action_confirm() overview_values = self.env['report.mrp.report_mo_overview'].get_report_values(mo.id) self.assertEqual(overview_values['data']['id'], mo.id, "Unexpected disparity between overview and MO data") + + def test_multi_step_component_forecast_availability(self): + """ + Test that the component availability is correcly forecasted + in multi step manufacturing + """ + # Configures the warehouse. + warehouse = self.env.ref('stock.warehouse0') + warehouse.manufacture_steps = 'pbm_sam' + final_product, component = self.product, self.product1 + bom = self.env['mrp.bom'].create({ + 'product_id': final_product.id, + 'product_tmpl_id': final_product.product_tmpl_id.id, + 'product_uom_id': final_product.uom_id.id, + 'product_qty': 1.0, + 'type': 'normal', + 'bom_line_ids': [ + Command.create({'product_id': component.id, 'product_qty': 10}), + ], + }) + # Creates a MO without any component in stock + mo_form = Form(self.env['mrp.production']) + mo_form.bom_id = bom + mo_form.product_qty = 2 + mo = mo_form.save() + mo.action_confirm() + self.assertEqual(mo.components_availability, 'Not Available') + self.assertEqual(mo.move_raw_ids.forecast_availability, -20.0) + self.env['stock.quant']._update_available_quantity(component, warehouse.lot_stock_id, 100) + # change the qty_producing to force a recompute of the availability + with Form(mo) as mo_form: + mo_form.qty_producing = 2.0 + self.assertEqual(mo.components_availability, 'Available') diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 6e35a0d16e08d..f9b0e5c64f512 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -2261,8 +2261,7 @@ def _get_forecast_availability_outgoing(self, warehouse, location_id=False): :return: a defaultdict of outgoing moves from warehouse for product_id in self, values are tuple (sum_qty_expected, max_date_expected) :rtype: defaultdict """ - wh_location_query = self.env['stock.location']._search([('id', 'child_of', location_id.id if location_id else warehouse.view_location_id.id)]) - + wh_location_query = self.env['stock.location']._search([('id', 'child_of', warehouse.view_location_id.id)]) forecast_lines = self.env['stock.forecasted_product_product']._get_report_lines(False, self.product_id.ids, wh_location_query, location_id or warehouse.lot_stock_id, read=False) result = defaultdict(lambda: (0.0, False)) for line in forecast_lines: