Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0][FIX] mrp_multi_level: fix kit/phantom planning #1367

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions mrp_multi_level/models/mrp_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,11 @@ def _compute_uom_id(self):
@api.depends("planned_order_ids", "planned_order_ids.qty_released")
def _compute_to_procure(self):
for rec in self:
rec.to_procure = sum(rec.planned_order_ids.mapped("mrp_qty")) - sum(
rec.planned_order_ids.mapped("qty_released")
rec.to_procure = (
0.0
if rec.supply_method == "phantom"
else sum(rec.planned_order_ids.mapped("mrp_qty"))
- sum(rec.planned_order_ids.mapped("qty_released"))
)

@api.depends(
Expand Down
1 change: 1 addition & 0 deletions mrp_multi_level/models/mrp_planned_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class MrpPlannedOrder(models.Model):
mrp_action = fields.Selection(
selection=[
("manufacture", "Manufacturing Order"),
("phantom", "Kit"),
("buy", "Purchase Order"),
("pull", "Pull From"),
("push", "Push To"),
Expand Down
4 changes: 0 additions & 4 deletions mrp_multi_level/models/product_mrp_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,3 @@ def _to_be_exploded(self):
def _get_locations(self):
self.ensure_one()
return self.mrp_area_id._get_locations()

def _should_create_planned_order(self):
self.ensure_one()
return not self.supply_method == "phantom"
57 changes: 52 additions & 5 deletions mrp_multi_level/tests/test_mrp_multi_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,11 +401,8 @@ def test_16_phantom_comp_planning(self):
sf_3_planned_order_1 = self.planned_order_obj.search(
[("product_mrp_area_id.product_id", "=", self.sf_3.id)]
)
self.assertEqual(len(sf_3_planned_order_1), 0)
sf_3_mrp_parameter = self.product_mrp_area_obj.search(
[("product_id", "=", self.sf_3.id)]
)
self.assertEqual(sf_3_mrp_parameter.supply_method, "phantom")
self.assertEqual(sf_3_planned_order_1.mrp_action, "phantom")
self.assertEqual(sf_3_planned_order_1.mrp_qty, 10.0)
# PP-3
pp_3_line_1 = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.pp_3.id)]
Expand Down Expand Up @@ -852,3 +849,53 @@ def test_24_prioritize_safety_stock_with_mrp_moves_today_grouped(self):
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)

def test_25_phantom_comp_on_hand(self):
"""
A phantom product with positive qty_available (which is computed from the
availability of its components) should not satisfy demand, because this leads
to double counting qty_available of its component products.
"""
quant = self.quant_obj.sudo().create(
{
"product_id": self.pp_3.id,
"inventory_quantity": 10.0,
"location_id": self.stock_location.id,
}
)
quant.action_apply_inventory()
quant = self.quant_obj.sudo().create(
{
"product_id": self.pp_4.id,
"inventory_quantity": 30.0,
"location_id": self.stock_location.id,
}
)
quant.action_apply_inventory()
self.assertEqual(self.sf_3.qty_available, 10.0)
self.mrp_multi_level_wiz.create({}).run_mrp_multi_level()
# PP-3
pp_3_line_1 = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.pp_3.id)]
)
self.assertEqual(len(pp_3_line_1), 1)
self.assertEqual(pp_3_line_1.demand_qty, 20.0)
self.assertEqual(pp_3_line_1.to_procure, 10.0)
pp_3_planned_orders = self.planned_order_obj.search(
[("product_mrp_area_id.product_id", "=", self.pp_3.id)]
)
self.assertEqual(len(pp_3_planned_orders), 1)
self.assertEqual(pp_3_planned_orders.mrp_qty, 10)
sf3_planned_orders = self.env["mrp.planned.order"].search(
[("product_id", "=", self.sf_3.id)]
)
self.assertEqual(len(sf3_planned_orders), 1)
# Trying to procure a kit planned order will have no effect.
procure_wizard = (
self.env["mrp.inventory.procure"]
.with_context(
active_model="mrp.planned.order", active_ids=sf3_planned_orders.ids
)
.create({})
)
self.assertEqual(len(procure_wizard.item_ids), 0)
6 changes: 5 additions & 1 deletion mrp_multi_level/views/mrp_planned_order_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
<field name="name">mrp.planned.order.tree</field>
<field name="model">mrp.planned.order</field>
<field name="arch" type="xml">
<tree decoration-info="fixed != True">
<tree
decoration-info="fixed != True and mrp_action != 'phantom'"
decoration-muted="mrp_action == 'phantom'"
>
<field name="name" />
<field name="origin" />
<field name="product_mrp_area_id" />
Expand All @@ -17,6 +20,7 @@
<field name="qty_released" />
<field name="mrp_qty" />
<field name="fixed" />
<field name="mrp_action" optional="hide" />
</tree>
</field>
</record>
Expand Down
2 changes: 2 additions & 0 deletions mrp_multi_level/wizards/mrp_inventory_procure.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def default_get(self, fields):
elif active_model == "mrp.planned.order":
mrp_planned_order_obj = self.env[active_model]
for line in mrp_planned_order_obj.browse(active_ids):
if line.mrp_action == "phantom":
continue
if line.qty_released < line.mrp_qty:
items += item_obj.create(self._prepare_item(line))
if items:
Expand Down
26 changes: 18 additions & 8 deletions mrp_multi_level/wizards/mrp_multi_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,10 +272,7 @@ def create_planned_order(
order_data = self._prepare_planned_order_data(
product_mrp_area_id, qty, mrp_date_supply, mrp_action_date, name, values
)
# Do not create planned order for products that are Kits
planned_order = False
if product_mrp_area_id._should_create_planned_order():
planned_order = self.env["mrp.planned.order"].create(order_data)
planned_order = self.env["mrp.planned.order"].create(order_data)
qty_ordered = qty_ordered + qty

if product_mrp_area_id._to_be_exploded():
Expand Down Expand Up @@ -535,7 +532,11 @@ def _get_qty_to_order(self, product_mrp_area, date, move_qty, onhand):
def _init_mrp_move_grouped_demand(self, product_mrp_area):
last_date = None
last_qty = 0.00
onhand = product_mrp_area.qty_available
onhand = (
0.0
if product_mrp_area.supply_method == "phantom"
else product_mrp_area.qty_available
)
grouping_delta = product_mrp_area.mrp_nbr_days
demand_origin = []

Expand Down Expand Up @@ -665,7 +666,11 @@ def _get_safety_stock_target_date(self, product_mrp_area):

@api.model
def _init_mrp_move_non_grouped_demand(self, product_mrp_area):
onhand = product_mrp_area.qty_available
onhand = (
0.0
if product_mrp_area.supply_method == "phantom"
else product_mrp_area.qty_available
)
for move in product_mrp_area.mrp_move_ids:
if self._exclude_move(move):
continue
Expand Down Expand Up @@ -814,7 +819,8 @@ def _prepare_mrp_inventory_data(
supply_qty = supply_qty_by_date.get(mdt, 0.0)
mrp_inventory_data["supply_qty"] = abs(supply_qty)
mrp_inventory_data["initial_on_hand_qty"] = on_hand_qty
on_hand_qty += supply_qty + demand_qty
if product_mrp_area.supply_method != "phantom":
on_hand_qty += supply_qty + demand_qty
mrp_inventory_data["final_on_hand_qty"] = on_hand_qty
# Consider that MRP plan is followed exactly:
running_availability += (
Expand Down Expand Up @@ -853,7 +859,11 @@ def _init_mrp_inventory(self, product_mrp_area):
[("product_mrp_area_id", "=", product_mrp_area.id)], order="due_date"
).mapped("due_date")
mrp_dates = set(moves_dates + action_dates)
on_hand_qty = product_mrp_area.qty_available
on_hand_qty = (
0.0
if product_mrp_area.supply_method == "phantom"
else product_mrp_area.qty_available
)
running_availability = on_hand_qty
mrp_inventory_vals = []
for mdt in sorted(mrp_dates):
Expand Down
Loading