From 9d1e489e5477987a4db86dfae7a0d8dbc984ca48 Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Wed, 16 Oct 2019 13:08:37 +0200 Subject: [PATCH] [12.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/common.py | 92 ++++++++++++++++++- mrp_multi_level/tests/test_mrp_multi_level.py | 14 +++ mrp_multi_level/wizards/mrp_multi_level.py | 3 +- 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/mrp_multi_level/tests/common.py b/mrp_multi_level/tests/common.py index a64cb1fc3..8db7567af 100644 --- a/mrp_multi_level/tests/common.py +++ b/mrp_multi_level/tests/common.py @@ -2,7 +2,7 @@ # (http://www.eficent.com) # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). -from datetime import datetime +from datetime import datetime, timedelta from odoo.tests.common import SavepointCase @@ -40,6 +40,7 @@ class TestMrpMultiLevelCommon(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 @@ -68,6 +69,19 @@ class TestMrpMultiLevelCommon(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_stock.route_warehouse0_buy').id @@ -132,6 +146,36 @@ class TestMrpMultiLevelCommon(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().replace(hour=0)) date_move = res.date() @@ -282,3 +326,49 @@ class TestMrpMultiLevelCommon(SavepointCase): 'groups_id': [(6, 0, [group.id for group in groups])] }) 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 diff --git a/mrp_multi_level/tests/test_mrp_multi_level.py b/mrp_multi_level/tests/test_mrp_multi_level.py index 558ed486c..027d796a0 100644 --- a/mrp_multi_level/tests/test_mrp_multi_level.py +++ b/mrp_multi_level/tests/test_mrp_multi_level.py @@ -250,3 +250,17 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon): prev = self.mrp_inventory_obj.search([ ('mrp_area_id', '!=', self.secondary_area.id)], limit=1) self.assertNotEqual(this.create_uid, prev.create_uid) + + 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 ebcf08855..301640de9 100644 --- a/mrp_multi_level/wizards/mrp_multi_level.py +++ b/mrp_multi_level/wizards/mrp_multi_level.py @@ -488,7 +488,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,