mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[FIX] mrp_multi_level: wrong safety stock intial order in grouped demand mode
Three tests cases modelize the issues being solved.
This commit is contained in:
committed by
BernatPForgeFlow
parent
11b3f934e7
commit
fc8ffcd594
@@ -521,5 +521,239 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon):
|
||||
test_vals[key],
|
||||
inv[key],
|
||||
f"unexpected value for {key}: {inv[key]} "
|
||||
f"(expected {test_vals[key]} on {inv.date}",
|
||||
f"(expected {test_vals[key]} on {inv.date})",
|
||||
)
|
||||
|
||||
def test_19_on_hand_with_lots(self):
|
||||
"""Check that on-hand is correctly computed when tracking by lots."""
|
||||
lots_line_1 = self.mrp_inventory_obj.search(
|
||||
[("product_mrp_area_id.product_id", "=", self.product_lots.id)]
|
||||
)
|
||||
self.assertEqual(len(lots_line_1), 1)
|
||||
self.assertEqual(lots_line_1.initial_on_hand_qty, 210)
|
||||
self.assertEqual(lots_line_1.final_on_hand_qty, 185)
|
||||
|
||||
def test_20_prioritize_safety_stock_grouped_1(self):
|
||||
"""Test grouped demand MRP but with a short nbr days.
|
||||
Safety stock should be ordered."""
|
||||
now = datetime.now()
|
||||
product = self.prod_test # has Buy route
|
||||
product.seller_ids[0].delay = 2 # set a purchase lead time
|
||||
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
|
||||
self.product_mrp_area_obj.create(
|
||||
{
|
||||
"product_id": product.id,
|
||||
"mrp_area_id": self.cases_area.id,
|
||||
"mrp_minimum_stock": 15,
|
||||
"mrp_nbr_days": 2,
|
||||
}
|
||||
)
|
||||
self._create_picking_out(
|
||||
product, 6.0, now + timedelta(days=3), location=self.cases_loc
|
||||
)
|
||||
self._create_picking_in(
|
||||
product, 10.0, now + timedelta(days=7), location=self.cases_loc
|
||||
)
|
||||
self._create_picking_out(
|
||||
product, 12.0, now + timedelta(days=14), location=self.cases_loc
|
||||
)
|
||||
self.mrp_multi_level_wiz.create(
|
||||
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
|
||||
).run_mrp_multi_level()
|
||||
inventory = self.mrp_inventory_obj.search(
|
||||
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
|
||||
)
|
||||
expected = [
|
||||
{
|
||||
"date": now.date(),
|
||||
"demand_qty": 0.0,
|
||||
"final_on_hand_qty": 5.0,
|
||||
"initial_on_hand_qty": 5.0,
|
||||
"running_availability": 15.0,
|
||||
"supply_qty": 0.0,
|
||||
"to_procure": 10.0,
|
||||
},
|
||||
{
|
||||
"date": now.date() + timedelta(days=3),
|
||||
"demand_qty": 6.0,
|
||||
"final_on_hand_qty": -1.0,
|
||||
"initial_on_hand_qty": 5.0,
|
||||
"running_availability": 15.0,
|
||||
"supply_qty": 0.0,
|
||||
"to_procure": 6.0,
|
||||
},
|
||||
{
|
||||
"date": now.date() + timedelta(days=7),
|
||||
"demand_qty": 0.0,
|
||||
"final_on_hand_qty": 9.0,
|
||||
"initial_on_hand_qty": -1.0,
|
||||
"running_availability": 25.0,
|
||||
"supply_qty": 10.0,
|
||||
"to_procure": 0.0,
|
||||
},
|
||||
{
|
||||
"date": now.date() + timedelta(days=14),
|
||||
"demand_qty": 12.0,
|
||||
"final_on_hand_qty": -3.0,
|
||||
"initial_on_hand_qty": 9.0,
|
||||
"running_availability": 15.0,
|
||||
"supply_qty": 0.0,
|
||||
"to_procure": 2.0,
|
||||
},
|
||||
]
|
||||
self.assertEqual(len(expected), len(inventory))
|
||||
for test_vals, inv in zip(expected, inventory):
|
||||
for key in test_vals:
|
||||
self.assertEqual(
|
||||
test_vals[key],
|
||||
inv[key],
|
||||
f"unexpected value for {key}: {inv[key]} "
|
||||
f"(expected {test_vals[key]} on {inv.date})",
|
||||
)
|
||||
|
||||
def test_21_prioritize_safety_stock_grouped_2(self):
|
||||
"""Test grouped demand MRP but with a longer nbr days.
|
||||
Safety stock should be ordered."""
|
||||
now = datetime.now()
|
||||
product = self.prod_test # has Buy route
|
||||
product.seller_ids[0].delay = 2 # set a purchase lead time
|
||||
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
|
||||
self.product_mrp_area_obj.create(
|
||||
{
|
||||
"product_id": product.id,
|
||||
"mrp_area_id": self.cases_area.id,
|
||||
"mrp_minimum_stock": 15,
|
||||
"mrp_nbr_days": 7,
|
||||
}
|
||||
)
|
||||
self._create_picking_out(
|
||||
product, 6.0, now + timedelta(days=3), location=self.cases_loc
|
||||
)
|
||||
self._create_picking_in(
|
||||
product, 10.0, now + timedelta(days=7), location=self.cases_loc
|
||||
)
|
||||
self._create_picking_out(
|
||||
product, 12.0, now + timedelta(days=12), location=self.cases_loc
|
||||
)
|
||||
self.mrp_multi_level_wiz.create(
|
||||
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
|
||||
).run_mrp_multi_level()
|
||||
inventory = self.mrp_inventory_obj.search(
|
||||
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
|
||||
)
|
||||
expected = [
|
||||
{
|
||||
"date": now.date(),
|
||||
"demand_qty": 0.0,
|
||||
"final_on_hand_qty": 5.0,
|
||||
"initial_on_hand_qty": 5.0,
|
||||
"running_availability": 21.0,
|
||||
"supply_qty": 0.0,
|
||||
"to_procure": 16.0,
|
||||
},
|
||||
{
|
||||
"date": now.date() + timedelta(days=3),
|
||||
"demand_qty": 6.0,
|
||||
"final_on_hand_qty": -1.0,
|
||||
"initial_on_hand_qty": 5.0,
|
||||
"running_availability": 15.0,
|
||||
"supply_qty": 0.0,
|
||||
"to_procure": 0.0,
|
||||
},
|
||||
{
|
||||
"date": now.date() + timedelta(days=7),
|
||||
"demand_qty": 0.0,
|
||||
"final_on_hand_qty": 9.0,
|
||||
"initial_on_hand_qty": -1.0,
|
||||
"running_availability": 27.0,
|
||||
"supply_qty": 10.0,
|
||||
"to_procure": 2.0,
|
||||
},
|
||||
{
|
||||
"date": now.date() + timedelta(days=12),
|
||||
"demand_qty": 12.0,
|
||||
"final_on_hand_qty": -3.0,
|
||||
"initial_on_hand_qty": 9.0,
|
||||
"running_availability": 15.0,
|
||||
"supply_qty": 0.0,
|
||||
"to_procure": 0.0,
|
||||
},
|
||||
]
|
||||
self.assertEqual(len(expected), len(inventory))
|
||||
for test_vals, inv in zip(expected, inventory):
|
||||
for key in test_vals:
|
||||
self.assertEqual(
|
||||
test_vals[key],
|
||||
inv[key],
|
||||
f"unexpected value for {key}: {inv[key]} "
|
||||
f"(expected {test_vals[key]} on {inv.date})",
|
||||
)
|
||||
|
||||
def test_22_prioritize_safety_stock_grouped_3(self):
|
||||
"""Test grouped demand MRP but with an existing incoming supply
|
||||
Safety stock should NOT be ordered."""
|
||||
now = datetime.now()
|
||||
product = self.prod_test # has Buy route
|
||||
product.seller_ids[0].delay = 2 # set a purchase lead time
|
||||
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
|
||||
self.product_mrp_area_obj.create(
|
||||
{
|
||||
"product_id": product.id,
|
||||
"mrp_area_id": self.cases_area.id,
|
||||
"mrp_minimum_stock": 15,
|
||||
"mrp_nbr_days": 7,
|
||||
}
|
||||
)
|
||||
self._create_picking_in(
|
||||
product, 30.0, now + timedelta(days=3), location=self.cases_loc
|
||||
)
|
||||
self._create_picking_out(
|
||||
product, 6.0, now + timedelta(days=7), location=self.cases_loc
|
||||
)
|
||||
self._create_picking_out(
|
||||
product, 12.0, now + timedelta(days=12), location=self.cases_loc
|
||||
)
|
||||
self.mrp_multi_level_wiz.create(
|
||||
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
|
||||
).run_mrp_multi_level()
|
||||
inventory = self.mrp_inventory_obj.search(
|
||||
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
|
||||
)
|
||||
expected = [
|
||||
{
|
||||
"date": now.date() + timedelta(days=3),
|
||||
"demand_qty": 0.0,
|
||||
"initial_on_hand_qty": 5.0,
|
||||
"final_on_hand_qty": 35.0,
|
||||
"running_availability": 35.0,
|
||||
"supply_qty": 30.0,
|
||||
"to_procure": 0.0,
|
||||
},
|
||||
{
|
||||
"date": now.date() + timedelta(days=7),
|
||||
"demand_qty": 6.0,
|
||||
"initial_on_hand_qty": 35.0,
|
||||
"final_on_hand_qty": 29.0,
|
||||
"running_availability": 29.0,
|
||||
"supply_qty": 0.0,
|
||||
"to_procure": 0.0,
|
||||
},
|
||||
{
|
||||
"date": now.date() + timedelta(days=12),
|
||||
"demand_qty": 12.0,
|
||||
"initial_on_hand_qty": 29.0,
|
||||
"final_on_hand_qty": 17.0,
|
||||
"running_availability": 17.0,
|
||||
"supply_qty": 0.0,
|
||||
"to_procure": 0.0,
|
||||
},
|
||||
]
|
||||
self.assertEqual(len(expected), len(inventory))
|
||||
for test_vals, inv in zip(expected, inventory):
|
||||
for key in test_vals:
|
||||
self.assertEqual(
|
||||
test_vals[key],
|
||||
inv[key],
|
||||
f"unexpected value for {key}: {inv[key]} "
|
||||
f"(expected {test_vals[key]} on {inv.date})",
|
||||
)
|
||||
|
||||
@@ -555,6 +555,37 @@ class MultiLevelMrp(models.TransientModel):
|
||||
onhand = product_mrp_area.qty_available
|
||||
grouping_delta = product_mrp_area.mrp_nbr_days
|
||||
demand_origin = []
|
||||
|
||||
if (
|
||||
product_mrp_area.mrp_move_ids
|
||||
and onhand < product_mrp_area.mrp_minimum_stock
|
||||
):
|
||||
last_date = self._get_safety_stock_target_date(product_mrp_area)
|
||||
demand_origin.append("Safety Stock")
|
||||
move = fields.first(product_mrp_area.mrp_move_ids)
|
||||
if last_date and (
|
||||
fields.Date.from_string(move.mrp_date)
|
||||
>= last_date + timedelta(days=grouping_delta)
|
||||
):
|
||||
name = _("Safety Stock")
|
||||
origin = ",".join(list({x for x in demand_origin if x}))
|
||||
qtytoorder = self._get_qty_to_order(
|
||||
product_mrp_area, last_date, 0, onhand
|
||||
)
|
||||
cm = self.create_action(
|
||||
product_mrp_area_id=product_mrp_area,
|
||||
mrp_date=last_date,
|
||||
mrp_qty=qtytoorder,
|
||||
name=name,
|
||||
values=dict(origin=origin),
|
||||
)
|
||||
qty_ordered = cm.get("qty_ordered", 0.0)
|
||||
onhand = onhand + qty_ordered
|
||||
last_date = None
|
||||
last_qty = 0.00
|
||||
nbr_create += 1
|
||||
demand_origin = []
|
||||
|
||||
for move in product_mrp_area.mrp_move_ids:
|
||||
if self._exclude_move(move):
|
||||
continue
|
||||
@@ -598,7 +629,7 @@ class MultiLevelMrp(models.TransientModel):
|
||||
) < product_mrp_area.mrp_minimum_stock or (
|
||||
onhand + last_qty
|
||||
) < product_mrp_area.mrp_minimum_stock:
|
||||
if not last_date or last_qty == 0.0:
|
||||
if not last_date:
|
||||
last_date = fields.Date.from_string(move.mrp_date)
|
||||
last_qty = move.mrp_qty
|
||||
else:
|
||||
@@ -629,9 +660,10 @@ class MultiLevelMrp(models.TransientModel):
|
||||
)
|
||||
qty_ordered = cm.get("qty_ordered", 0.0)
|
||||
onhand += qty_ordered
|
||||
last_qty -= qty_ordered
|
||||
nbr_create += 1
|
||||
|
||||
if onhand < product_mrp_area.mrp_minimum_stock:
|
||||
if (onhand + last_qty) < product_mrp_area.mrp_minimum_stock:
|
||||
mrp_date = self._get_safety_stock_target_date(product_mrp_area)
|
||||
qtytoorder = self._get_qty_to_order(product_mrp_area, mrp_date, 0, onhand)
|
||||
name = _("Safety Stock")
|
||||
|
||||
Reference in New Issue
Block a user