From 127017798c601c69bf9c1e8f9f01380a57e30b81 Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Wed, 16 Oct 2019 13:08:37 +0200 Subject: [PATCH] [11.0][FIX] mrp_multi_level: when grouping demand, if supply and demand moves have the same date it can happen that the supply is effectively ignored if considered as staring move of the grouping and there are more groups to be done after it. A test case include in this fix depicts in detail the the problem and ensures no regression. --- mrp_multi_level/tests/test_mrp_multi_level.py | 105 +++++++++++++++++- mrp_multi_level/wizards/mrp_multi_level.py | 3 +- 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/mrp_multi_level/tests/test_mrp_multi_level.py b/mrp_multi_level/tests/test_mrp_multi_level.py index 1a43a1025..a7293e26d 100644 --- a/mrp_multi_level/tests/test_mrp_multi_level.py +++ b/mrp_multi_level/tests/test_mrp_multi_level.py @@ -43,6 +43,7 @@ class TestMrpMultiLevel(SavepointCase): cls.stock_location = cls.wh.lot_stock_id cls.customer_location = cls.env.ref( 'stock.stock_location_customers') + cls.supplier_location = cls.env.ref('stock.stock_location_suppliers') cls.calendar = cls.env.ref('resource.resource_calendar_std') # Add calendar to WH: cls.wh.calendar_id = cls.calendar @@ -71,6 +72,19 @@ class TestMrpMultiLevel(SavepointCase): 'warehouse_id': cls.wh.id, 'location_id': cls.sec_loc.id, }) + # Create an area for design special cases and test them, different + # cases will be expected to not share products, this way each case + # can be isolated. + cls.cases_loc = cls.loc_obj.create({ + 'name': 'Special Cases location', + 'usage': 'internal', + 'location_id': cls.wh.view_location_id.id, + }) + cls.cases_area = cls.mrp_area_obj.create({ + 'name': 'Special Cases Tests', + 'warehouse_id': cls.wh.id, + 'location_id': cls.cases_loc.id, + }) # Create products: route_buy = cls.env.ref('purchase.route_warehouse0_buy').id @@ -135,6 +149,36 @@ class TestMrpMultiLevel(SavepointCase): 'mrp_maximum_order_qty': 500.0, 'mrp_qty_multiple': 25.0, }) + # Create more products to test special corner case scenarios: + cls.product_scenario_1 = cls.product_obj.create({ + 'name': 'Product Special Scenario 1', + 'type': 'product', + 'list_price': 100.0, + 'route_ids': [(6, 0, [route_buy])], + 'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 20.0})], + }) + cls.product_mrp_area_obj.create({ + 'product_id': cls.product_scenario_1.id, + 'mrp_area_id': cls.cases_area.id, + 'mrp_nbr_days': 7, + 'mrp_qty_multiple': 5.0, + }) + + # Create pickings for Scenario 1: + dt_base = cls.calendar.plan_days(3 + 1, datetime.today()) + cls._create_picking_in( + cls.product_scenario_1, 87, dt_base, location=cls.cases_loc) + dt_bit_later = dt_base + timedelta(hours=1) + cls._create_picking_out( + cls.product_scenario_1, 124, dt_bit_later, location=cls.cases_loc) + dt_base_2 = cls.calendar.plan_days(3 + 1, datetime.today()) + cls._create_picking_out( + cls.product_scenario_1, 90, dt_base_2, location=cls.cases_loc) + + dt_next_group = cls.calendar.plan_days(10 + 1, datetime.today()) + cls._create_picking_out( + cls.product_scenario_1, 18, dt_next_group, location=cls.cases_loc) + # Create test picking for FP-1 and FP-2: res = cls.calendar.plan_days(7+1, datetime.today()) date_move = res.date() @@ -299,6 +343,52 @@ class TestMrpMultiLevel(SavepointCase): }) return user + @classmethod + def _create_picking_in(cls, product, qty, date_move, location=None): + if not location: + location = cls.stock_location + picking = cls.stock_picking_obj.create({ + 'picking_type_id': cls.env.ref('stock.picking_type_in').id, + 'location_id': cls.supplier_location.id, + 'location_dest_id': location.id, + 'move_lines': [ + (0, 0, { + 'name': 'Test Move', + 'product_id': product.id, + 'date_expected': date_move, + 'date': date_move, + 'product_uom': product.uom_id.id, + 'product_uom_qty': qty, + 'location_id': cls.supplier_location.id, + 'location_dest_id': location.id, + })], + }) + picking.action_confirm() + return picking + + @classmethod + def _create_picking_out(cls, product, qty, date_move, location=None): + if not location: + location = cls.stock_location + picking = cls.stock_picking_obj.create({ + 'picking_type_id': cls.env.ref('stock.picking_type_out').id, + 'location_id': location.id, + 'location_dest_id': cls.customer_location.id, + 'move_lines': [ + (0, 0, { + 'name': 'Test Move', + 'product_id': product.id, + 'date_expected': date_move, + 'date': date_move, + 'product_uom': product.uom_id.id, + 'product_uom_qty': qty, + 'location_id': location.id, + 'location_dest_id': cls.customer_location.id, + })], + }) + picking.action_confirm() + return picking + def test_01_mrp_levels(self): """Tests computation of MRP levels.""" self.assertEqual(self.fp_1.llc, 0) @@ -572,5 +662,16 @@ class TestMrpMultiLevel(SavepointCase): ('mrp_area_id', '!=', self.secondary_area.id)], limit=1) self.assertNotEqual(this.create_uid, prev.create_uid) - # TODO: test procure wizard: pos, multiple... - # TODO: test multiple destination IDS:... + def test_11_special_scenario_1(self): + """When grouping demand supply and demand are in the same day but + supply goes first.""" + moves = self.mrp_move_obj.search([ + ('product_id', '=', self.product_scenario_1.id)]) + self.assertEqual(len(moves), 4) + mrp_invs = self.mrp_inventory_obj.search([ + ('product_id', '=', self.product_scenario_1.id)]) + self.assertEqual(len(mrp_invs), 2) + # Net needs = 124 + 90 - 87 = 127 -> 130 (because of qty multiple) + self.assertEqual(mrp_invs[0].to_procure, 130) + # Net needs = 18, available on-hand = 3 -> 15 + self.assertEqual(mrp_invs[1].to_procure, 15) diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py index ca9c84c2a..5c25035ce 100644 --- a/mrp_multi_level/wizards/mrp_multi_level.py +++ b/mrp_multi_level/wizards/mrp_multi_level.py @@ -544,7 +544,8 @@ class MultiLevelMrp(models.TransientModel): or (onhand + last_qty) < product_mrp_area.mrp_minimum_stock): name = 'Grouped Demand for %d Days' % grouping_delta - qtytoorder = product_mrp_area.mrp_minimum_stock - last_qty + qtytoorder = product_mrp_area.mrp_minimum_stock - \ + onhand - last_qty cm = self.create_action( product_mrp_area_id=product_mrp_area, mrp_date=last_date,