mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[FIX] mrp_multi_level: Prioritize safety stock with mrp moves today
If I have 0 units, my safety stock is 5 units and today I have a supply for 10 units, the procurement recommendation should be 0 units
This commit is contained in:
@@ -757,3 +757,88 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon):
|
|||||||
f"unexpected value for {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_23_prioritize_safety_stock_with_mrp_moves_today(self):
|
||||||
|
"""Test MRP but with moves today. 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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self._create_picking_out(product, 10.0, now, location=self.cases_loc)
|
||||||
|
self._create_picking_in(product, 20.0, now, 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": 10.0,
|
||||||
|
"final_on_hand_qty": 15.0,
|
||||||
|
"initial_on_hand_qty": 5.0,
|
||||||
|
"running_availability": 15.0,
|
||||||
|
"supply_qty": 20.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_24_prioritize_safety_stock_with_mrp_moves_today_grouped(self):
|
||||||
|
"""Test grouped demand MRP but with moves today. 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": 2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self._create_picking_out(product, 10.0, now, location=self.cases_loc)
|
||||||
|
self._create_picking_in(product, 20.0, now, 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": 10.0,
|
||||||
|
"final_on_hand_qty": 15.0,
|
||||||
|
"initial_on_hand_qty": 5.0,
|
||||||
|
"running_availability": 15.0,
|
||||||
|
"supply_qty": 20.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})",
|
||||||
|
)
|
||||||
|
|||||||
@@ -532,7 +532,7 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
return product_mrp_area.mrp_minimum_stock - onhand - move_qty
|
return product_mrp_area.mrp_minimum_stock - onhand - move_qty
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _init_mrp_move_grouped_demand(self, nbr_create, product_mrp_area):
|
def _init_mrp_move_grouped_demand(self, product_mrp_area):
|
||||||
last_date = None
|
last_date = None
|
||||||
last_qty = 0.00
|
last_qty = 0.00
|
||||||
onhand = product_mrp_area.qty_available
|
onhand = product_mrp_area.qty_available
|
||||||
@@ -566,7 +566,6 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
onhand = onhand + qty_ordered
|
onhand = onhand + qty_ordered
|
||||||
last_date = None
|
last_date = None
|
||||||
last_qty = 0.00
|
last_qty = 0.00
|
||||||
nbr_create += 1
|
|
||||||
demand_origin = []
|
demand_origin = []
|
||||||
|
|
||||||
for move in product_mrp_area.mrp_move_ids:
|
for move in product_mrp_area.mrp_move_ids:
|
||||||
@@ -605,7 +604,6 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
onhand = onhand + last_qty + qty_ordered
|
onhand = onhand + last_qty + qty_ordered
|
||||||
last_date = None
|
last_date = None
|
||||||
last_qty = 0.00
|
last_qty = 0.00
|
||||||
nbr_create += 1
|
|
||||||
demand_origin = []
|
demand_origin = []
|
||||||
if (
|
if (
|
||||||
onhand + last_qty + move.mrp_qty
|
onhand + last_qty + move.mrp_qty
|
||||||
@@ -644,7 +642,6 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
qty_ordered = cm.get("qty_ordered", 0.0)
|
qty_ordered = cm.get("qty_ordered", 0.0)
|
||||||
onhand += qty_ordered
|
onhand += qty_ordered
|
||||||
last_qty -= qty_ordered
|
last_qty -= qty_ordered
|
||||||
nbr_create += 1
|
|
||||||
|
|
||||||
if (onhand + last_qty) < 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)
|
mrp_date = self._get_safety_stock_target_date(product_mrp_area)
|
||||||
@@ -659,9 +656,6 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
)
|
)
|
||||||
qty_ordered = cm["qty_ordered"]
|
qty_ordered = cm["qty_ordered"]
|
||||||
onhand += qty_ordered
|
onhand += qty_ordered
|
||||||
nbr_create += 1
|
|
||||||
|
|
||||||
return nbr_create
|
|
||||||
|
|
||||||
def _get_safety_stock_target_date(self, product_mrp_area):
|
def _get_safety_stock_target_date(self, product_mrp_area):
|
||||||
"""Get the date at which the safety stock rebuild should be targeted
|
"""Get the date at which the safety stock rebuild should be targeted
|
||||||
@@ -670,12 +664,14 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
return date.today()
|
return date.today()
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _init_mrp_move_non_grouped_demand(self, nbr_create, product_mrp_area):
|
def _init_mrp_move_non_grouped_demand(self, product_mrp_area):
|
||||||
onhand = product_mrp_area.qty_available
|
onhand = product_mrp_area.qty_available
|
||||||
for move in product_mrp_area.mrp_move_ids:
|
for move in product_mrp_area.mrp_move_ids:
|
||||||
if self._exclude_move(move):
|
if self._exclude_move(move):
|
||||||
continue
|
continue
|
||||||
if onhand < product_mrp_area.mrp_minimum_stock:
|
# This works because mrp moves are ordered by:
|
||||||
|
# product_mrp_area_id, mrp_date, mrp_type desc, id
|
||||||
|
if onhand + move.mrp_qty < product_mrp_area.mrp_minimum_stock:
|
||||||
qtytoorder = self._get_qty_to_order(
|
qtytoorder = self._get_qty_to_order(
|
||||||
product_mrp_area,
|
product_mrp_area,
|
||||||
self._get_safety_stock_target_date(product_mrp_area),
|
self._get_safety_stock_target_date(product_mrp_area),
|
||||||
@@ -692,7 +688,6 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
)
|
)
|
||||||
qty_ordered = cm["qty_ordered"]
|
qty_ordered = cm["qty_ordered"]
|
||||||
onhand += qty_ordered
|
onhand += qty_ordered
|
||||||
nbr_create += 1
|
|
||||||
|
|
||||||
qtytoorder = self._get_qty_to_order(
|
qtytoorder = self._get_qty_to_order(
|
||||||
product_mrp_area, move.mrp_date, move.mrp_qty, onhand
|
product_mrp_area, move.mrp_date, move.mrp_qty, onhand
|
||||||
@@ -707,7 +702,6 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
)
|
)
|
||||||
qty_ordered = cm["qty_ordered"]
|
qty_ordered = cm["qty_ordered"]
|
||||||
onhand += move.mrp_qty + qty_ordered
|
onhand += move.mrp_qty + qty_ordered
|
||||||
nbr_create += 1
|
|
||||||
else:
|
else:
|
||||||
onhand += move.mrp_qty
|
onhand += move.mrp_qty
|
||||||
if onhand < product_mrp_area.mrp_minimum_stock:
|
if onhand < product_mrp_area.mrp_minimum_stock:
|
||||||
@@ -723,9 +717,6 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
)
|
)
|
||||||
qty_ordered = cm["qty_ordered"]
|
qty_ordered = cm["qty_ordered"]
|
||||||
onhand += qty_ordered
|
onhand += qty_ordered
|
||||||
nbr_create += 1
|
|
||||||
|
|
||||||
return nbr_create
|
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _exclude_move(self, move):
|
def _exclude_move(self, move):
|
||||||
@@ -748,33 +739,10 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
llc += 1
|
llc += 1
|
||||||
|
|
||||||
for product_mrp_area in product_mrp_areas:
|
for product_mrp_area in product_mrp_areas:
|
||||||
nbr_create = 0
|
|
||||||
onhand = product_mrp_area.qty_available
|
|
||||||
|
|
||||||
if product_mrp_area.mrp_nbr_days == 0:
|
if product_mrp_area.mrp_nbr_days == 0:
|
||||||
nbr_create = self._init_mrp_move_non_grouped_demand(
|
self._init_mrp_move_non_grouped_demand(product_mrp_area)
|
||||||
nbr_create, product_mrp_area
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
nbr_create = self._init_mrp_move_grouped_demand(
|
self._init_mrp_move_grouped_demand(product_mrp_area)
|
||||||
nbr_create, product_mrp_area
|
|
||||||
)
|
|
||||||
|
|
||||||
if onhand < product_mrp_area.mrp_minimum_stock and nbr_create == 0:
|
|
||||||
mrp_date = date.today()
|
|
||||||
qtytoorder = self._get_qty_to_order(
|
|
||||||
product_mrp_area, mrp_date, 0, onhand
|
|
||||||
)
|
|
||||||
name = _("Safety Stock")
|
|
||||||
cm = self.create_action(
|
|
||||||
product_mrp_area_id=product_mrp_area,
|
|
||||||
mrp_date=mrp_date,
|
|
||||||
mrp_qty=qtytoorder,
|
|
||||||
name=name,
|
|
||||||
values=dict(origin=name),
|
|
||||||
)
|
|
||||||
qty_ordered = cm["qty_ordered"]
|
|
||||||
onhand += qty_ordered
|
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
log_msg = "MRP Calculation LLC {} Finished - Nbr. products: {}".format(
|
log_msg = "MRP Calculation LLC {} Finished - Nbr. products: {}".format(
|
||||||
|
|||||||
Reference in New Issue
Block a user