[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.
This commit is contained in:
Lois Rilo
2019-10-16 13:08:37 +02:00
committed by Lois Rilo
parent 5da1d99fe2
commit 9d1e489e54
3 changed files with 107 additions and 2 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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,