From 6ee297312e0546825aca9dba366db04309072a22 Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Mon, 14 Feb 2022 19:44:45 +0100 Subject: [PATCH 1/2] [FIX] mrp_multi_level_estimate: do not overstate estimates when group days of estimates is greater than the lengh of the date range in the estimate, the estimate is overstated and can generate unexpected result. Enhance help message to highligh this and prevent it from happening. --- .../models/product_mrp_area.py | 5 +- .../tests/test_mrp_multi_level_estimate.py | 47 +++++++++++++++++-- .../wizards/mrp_multi_level.py | 11 ++++- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/mrp_multi_level_estimate/models/product_mrp_area.py b/mrp_multi_level_estimate/models/product_mrp_area.py index 0831b6db8..88ceee090 100644 --- a/mrp_multi_level_estimate/models/product_mrp_area.py +++ b/mrp_multi_level_estimate/models/product_mrp_area.py @@ -12,8 +12,9 @@ class ProductMRPArea(models.Model): string="Group Days of Estimates", default=1, help="The days to group your estimates as demand for the MRP." - "It can be different from the lenght of the date ranges you " - "use in the estimates.", + "It can be different from the length of the date ranges you " + "use in the estimates but it should not be greater, in that case" + "only grouping until the total lenght of the date range will be done.", ) _sql_constraints = [ diff --git a/mrp_multi_level_estimate/tests/test_mrp_multi_level_estimate.py b/mrp_multi_level_estimate/tests/test_mrp_multi_level_estimate.py index fe0bd3ec8..280c9a47b 100644 --- a/mrp_multi_level_estimate/tests/test_mrp_multi_level_estimate.py +++ b/mrp_multi_level_estimate/tests/test_mrp_multi_level_estimate.py @@ -29,7 +29,7 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon): "location_id": cls.estimate_loc.id, } ) - cls.product_mrp_area_obj.create( + cls.test_mrp_parameter = cls.product_mrp_area_obj.create( { "product_id": cls.prod_test.id, "mrp_area_id": cls.estimate_area.id, @@ -113,8 +113,8 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon): ) self.assertEqual(len(inventories), 18) - def test_02_group_demand_estimates(self): - """Test demand grouping functionality, `nbr_days`.""" + def test_02_demand_estimates_group_plans(self): + """Test requirement grouping functionality, `nbr_days`.""" estimates = self.estimate_obj.search( [ ("product_id", "=", self.prod_test.id), @@ -146,3 +146,44 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon): self.assertIn(abs(week_2_expected), quantities) week_3_expected = sum(moves_from_estimates[14:].mapped("mrp_qty")) self.assertIn(abs(week_3_expected), quantities) + + def test_03_group_demand_estimates(self): + """Test demand grouping functionality, `group_estimate_days`.""" + self.test_mrp_parameter.group_estimate_days = 7 + self.mrp_multi_level_wiz.create({}).run_mrp_multi_level() + estimates = self.estimate_obj.search( + [ + ("product_id", "=", self.prod_test.id), + ("location_id", "=", self.estimate_loc.id), + ] + ) + self.assertEqual(len(estimates), 3) + moves = self.mrp_move_obj.search( + [ + ("product_id", "=", self.prod_test.id), + ("mrp_area_id", "=", self.estimate_area.id), + ] + ) + # 3 weekly estimates, demand from estimates grouped in batches of 7 + # days = 3 days of estimates mrp moves: + moves_from_estimates = moves.filtered(lambda m: m.mrp_type == "d") + self.assertEqual(len(moves_from_estimates), 3) + # 210 weekly -> 30 daily -> 30 * 4 days not consumed = 120 + self.assertEqual(moves_from_estimates[0].mrp_qty, -120) + self.assertEqual(moves_from_estimates[1].mrp_qty, -280) + self.assertEqual(moves_from_estimates[2].mrp_qty, -350) + # Test group_estimate_days greater than date range, it should not + # generate greater demand. + self.test_mrp_parameter.group_estimate_days = 10 + self.mrp_multi_level_wiz.create({}).run_mrp_multi_level() + moves = self.mrp_move_obj.search( + [ + ("product_id", "=", self.prod_test.id), + ("mrp_area_id", "=", self.estimate_area.id), + ] + ) + moves_from_estimates = moves.filtered(lambda m: m.mrp_type == "d") + self.assertEqual(len(moves_from_estimates), 3) + self.assertEqual(moves_from_estimates[0].mrp_qty, -120) + self.assertEqual(moves_from_estimates[1].mrp_qty, -280) + self.assertEqual(moves_from_estimates[2].mrp_qty, -350) diff --git a/mrp_multi_level_estimate/wizards/mrp_multi_level.py b/mrp_multi_level_estimate/wizards/mrp_multi_level.py index 6e56af6ce..c784538b1 100644 --- a/mrp_multi_level_estimate/wizards/mrp_multi_level.py +++ b/mrp_multi_level_estimate/wizards/mrp_multi_level.py @@ -23,6 +23,15 @@ class MultiLevelMrp(models.TransientModel): precision_rounding=product_mrp_area.product_id.uom_id.rounding, rounding_method="HALF-UP", ) + today = fields.Date.today() + days_consumed = 0 + if product_mrp_area.group_estimate_days > 1: + start = estimate.date_from + if start < today: + days_consumed = (today - start).days + group_estimate_days = min( + product_mrp_area.group_estimate_days, estimate.duration - days_consumed + ) return { "mrp_area_id": product_mrp_area.mrp_area_id.id, "product_id": product_mrp_area.product_id.id, @@ -31,7 +40,7 @@ class MultiLevelMrp(models.TransientModel): "purchase_order_id": None, "purchase_line_id": None, "stock_move_id": None, - "mrp_qty": -daily_qty * product_mrp_area.group_estimate_days, + "mrp_qty": -daily_qty * group_estimate_days, "current_qty": -daily_qty, "mrp_date": date, "current_date": date, From 5eb53b9992ec6f52ce22ac10d466749517460b16 Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Wed, 16 Feb 2022 18:09:13 +0100 Subject: [PATCH 2/2] [FIX] mrp_multi_level_estimate: rounding issue Rounding should be done at the end of the calculation, using the daily quantity already rounded can lead to errors. --- .../tests/test_mrp_multi_level_estimate.py | 40 +++++++++++++++++++ .../wizards/mrp_multi_level.py | 8 +++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/mrp_multi_level_estimate/tests/test_mrp_multi_level_estimate.py b/mrp_multi_level_estimate/tests/test_mrp_multi_level_estimate.py index 280c9a47b..b143f0f9a 100644 --- a/mrp_multi_level_estimate/tests/test_mrp_multi_level_estimate.py +++ b/mrp_multi_level_estimate/tests/test_mrp_multi_level_estimate.py @@ -14,6 +14,8 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon): super().setUpClass() cls.estimate_obj = cls.env["stock.demand.estimate"] + cls.uom_unit = cls.env.ref("uom.product_uom_unit") + # Create new clean area: cls.estimate_loc = cls.loc_obj.create( { @@ -187,3 +189,41 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon): self.assertEqual(moves_from_estimates[0].mrp_qty, -120) self.assertEqual(moves_from_estimates[1].mrp_qty, -280) self.assertEqual(moves_from_estimates[2].mrp_qty, -350) + + def test_04_group_demand_estimates_rounding(self): + """Test demand grouping functionality, `group_estimate_days` and rounding.""" + self.test_mrp_parameter.group_estimate_days = 7 + self.uom_unit.rounding = 1.00 + + estimates = self.estimate_obj.search( + [ + ("product_id", "=", self.prod_test.id), + ("location_id", "=", self.estimate_loc.id), + ] + ) + self.assertEqual(len(estimates), 3) + # Change qty of estimates to quantities that divided by 7 days return a decimal result + qty = 400 + for estimate in estimates: + estimate.product_uom_qty = qty + qty += 100 + + self.mrp_multi_level_wiz.create({}).run_mrp_multi_level() + moves = self.mrp_move_obj.search( + [ + ("product_id", "=", self.prod_test.id), + ("mrp_area_id", "=", self.estimate_area.id), + ] + ) + # 3 weekly estimates, demand from estimates grouped in batches of 7 + # days = 3 days of estimates mrp moves: + moves_from_estimates = moves.filtered(lambda m: m.mrp_type == "d") + self.assertEqual(len(moves_from_estimates), 3) + # Rounding should be done at the end of the calculation, using the daily + # quantity already rounded can lead to errors. + # 400 weekly -> 57.41 daily -> 57.41 * 4 days not consumed = 228,57 = 229 + self.assertEqual(moves_from_estimates[0].mrp_qty, -229) + # 500 weekly -> 71.42 daily -> 71,42 * 7 = 500 + self.assertEqual(moves_from_estimates[1].mrp_qty, -500) + # 600 weekly -> 85.71 daily -> 85.71 * 7 = 600 + self.assertEqual(moves_from_estimates[2].mrp_qty, -600) diff --git a/mrp_multi_level_estimate/wizards/mrp_multi_level.py b/mrp_multi_level_estimate/wizards/mrp_multi_level.py index c784538b1..8a6c68783 100644 --- a/mrp_multi_level_estimate/wizards/mrp_multi_level.py +++ b/mrp_multi_level_estimate/wizards/mrp_multi_level.py @@ -18,6 +18,7 @@ class MultiLevelMrp(models.TransientModel): def _prepare_mrp_move_data_from_estimate(self, estimate, product_mrp_area, date): mrp_type = "d" origin = "fc" + daily_qty_unrounded = estimate.daily_qty daily_qty = float_round( estimate.daily_qty, precision_rounding=product_mrp_area.product_id.uom_id.rounding, @@ -32,6 +33,11 @@ class MultiLevelMrp(models.TransientModel): group_estimate_days = min( product_mrp_area.group_estimate_days, estimate.duration - days_consumed ) + mrp_qty = float_round( + daily_qty_unrounded * group_estimate_days, + precision_rounding=product_mrp_area.product_id.uom_id.rounding, + rounding_method="HALF-UP", + ) return { "mrp_area_id": product_mrp_area.mrp_area_id.id, "product_id": product_mrp_area.product_id.id, @@ -40,7 +46,7 @@ class MultiLevelMrp(models.TransientModel): "purchase_order_id": None, "purchase_line_id": None, "stock_move_id": None, - "mrp_qty": -daily_qty * group_estimate_days, + "mrp_qty": -mrp_qty, "current_qty": -daily_qty, "mrp_date": date, "current_date": date,