From dc9f943e734a5798d09db1812a2b56500d4a5265 Mon Sep 17 00:00:00 2001 From: dkendall Date: Wed, 10 Jun 2026 20:36:13 -0400 Subject: [PATCH] [FIX] ddmrp: aggregate future actual demand in product UoM The 19.0 migration switched _calc_adu_future_demand from summing product_qty (product UoM) to product_uom_qty (move UoM). A confirmed future delivery of 5 Dozen then contributed 5 instead of 60 to the ADU, undersizing buffers for any non-unit secondary UoM. The past-demand path already aggregates in product UoM (quantity_product_uom). --- ddmrp/models/stock_buffer.py | 2 +- ddmrp/tests/test_ddmrp.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ddmrp/models/stock_buffer.py b/ddmrp/models/stock_buffer.py index e456829ab..1c2f45fe4 100644 --- a/ddmrp/models/stock_buffer.py +++ b/ddmrp/models/stock_buffer.py @@ -1550,7 +1550,7 @@ def _calc_adu_future_demand(self): elif self.adu_calculation_method.source_future == "actual": domain = self._future_moves_domain(date_from, date_to, locations) for [total] in self.env["stock.move"]._read_group( - domain, aggregates=["product_uom_qty:sum"] + domain, aggregates=["product_qty:sum"] ): qty += total or 0.0 return qty / horizon diff --git a/ddmrp/tests/test_ddmrp.py b/ddmrp/tests/test_ddmrp.py index 289dc5aa0..d3ec0ab68 100644 --- a/ddmrp/tests/test_ddmrp.py +++ b/ddmrp/tests/test_ddmrp.py @@ -1533,3 +1533,26 @@ def test_50_chart_execution_no_product_no_crash(self): data = json.loads(form.ddmrp_chart_execution) self.assertEqual(data["div"], "") self.assertEqual(data["script"], "") + + def test_51_adu_calculation_future_actual_move_uom(self): + """Future actual demand is aggregated in the product UoM. + + A move expressed in a secondary UoM (Dozen) must contribute its + product-UoM quantity to the ADU, not its move-UoM quantity. + """ + method = self.aducalcmethodModel.create( + { + "name": "Future actual demand (120 days)", + "method": "future", + "source_future": "actual", + "horizon_future": 120, + "company_id": self.main_company.id, + } + ) + self.buffer_a.adu_calculation_method = method.id + days = 30 + date_move = self.calendar.plan_days(+1 * days + 1, datetime.today()) + self.create_pickingoutA(date_move, 5, uom=self.dozen_unit) + self.bufferModel.cron_ddmrp_adu() + # 5 Dozen = 60 Units over the 120-day horizon + self.assertEqual(self.buffer_a.adu, 60 / 120)