diff --git a/mrp_multi_level/demo/mrp_bom_demo.xml b/mrp_multi_level/demo/mrp_bom_demo.xml
index 21e90a822..e2639cc08 100644
--- a/mrp_multi_level/demo/mrp_bom_demo.xml
+++ b/mrp_multi_level/demo/mrp_bom_demo.xml
@@ -40,8 +40,30 @@
5
-
-
+
+
+
+
+
+ 5
+
+
+
+ 2
+
+ 5
+
+
+
+
+ 2
+
+ 5
+
+
+
+
+
5
+
+
+
+
+ phantom
+ 5
+
+
+
+ 1
+
+ 5
+
+
+
+
+ 3
+
+ 5
+
+
diff --git a/mrp_multi_level/demo/product_mrp_area_demo.xml b/mrp_multi_level/demo/product_mrp_area_demo.xml
index fdb6a264c..c14a4ef80 100644
--- a/mrp_multi_level/demo/product_mrp_area_demo.xml
+++ b/mrp_multi_level/demo/product_mrp_area_demo.xml
@@ -8,6 +8,10 @@
+
+
+
+
@@ -32,6 +36,10 @@
+
+
+
+
@@ -40,6 +48,14 @@
+
+
+
+
+
+
+
+
diff --git a/mrp_multi_level/demo/product_product_demo.xml b/mrp_multi_level/demo/product_product_demo.xml
index f99f38e11..f9b45c103 100644
--- a/mrp_multi_level/demo/product_product_demo.xml
+++ b/mrp_multi_level/demo/product_product_demo.xml
@@ -24,7 +24,20 @@
eval="[(6, 0, [ref('mrp.route_warehouse0_manufacture')])]"
/>
-
+
+ FP-3
+
+ product
+
+
+ 3
+
+
+
+
+
+ SF-3
+
+ product
+
+
+ 3
+
+
+
PP-1
@@ -77,6 +103,31 @@
eval="[(6, 0, [ref('purchase_stock.route_warehouse0_buy')])]"
/>
+
+
+ PP-3
+
+ product
+
+
+
+
+
+
+ PP-4
+
+ product
+
+
+
+
+
AV-11 steel
diff --git a/mrp_multi_level/demo/product_supplierinfo_demo.xml b/mrp_multi_level/demo/product_supplierinfo_demo.xml
index c29460900..eaecfa523 100644
--- a/mrp_multi_level/demo/product_supplierinfo_demo.xml
+++ b/mrp_multi_level/demo/product_supplierinfo_demo.xml
@@ -42,4 +42,20 @@
0
100
+
+
+
+
+ 2
+ 0
+ 10
+
+
+
+
+
+ 3
+ 0
+ 80
+
diff --git a/mrp_multi_level/models/product_mrp_area.py b/mrp_multi_level/models/product_mrp_area.py
index 42c94bb94..c01d56421 100644
--- a/mrp_multi_level/models/product_mrp_area.py
+++ b/mrp_multi_level/models/product_mrp_area.py
@@ -78,6 +78,7 @@ class ProductMRPArea(models.Model):
("buy", "Buy"),
("none", "Undefined"),
("manufacture", "Produce"),
+ ("phantom", "Kit"),
("pull", "Pull From"),
("push", "Push To"),
("pull_push", "Pull & Push"),
@@ -182,7 +183,14 @@ class ProductMRPArea(models.Model):
"company_id": rec.mrp_area_id.company_id,
}
rule = group_obj._get_rule(rec.product_id, proc_loc, values)
- rec.supply_method = rule.action if rule else "none"
+ if (
+ rule.action == "manufacture"
+ and rec.product_id.product_tmpl_id.bom_ids
+ and rec.product_id.product_tmpl_id.bom_ids[0].type == "phantom"
+ ):
+ rec.supply_method = "phantom"
+ else:
+ rec.supply_method = rule.action if rule else "none"
@api.depends("supply_method", "product_id.route_ids", "product_id.seller_ids")
def _compute_main_supplier(self):
@@ -263,7 +271,4 @@ class ProductMRPArea(models.Model):
def _to_be_exploded(self):
self.ensure_one()
- if self.supply_method == "manufacture":
- return True
- else:
- return False
+ return self.supply_method in ["manufacture", "phantom"]
diff --git a/mrp_multi_level/tests/common.py b/mrp_multi_level/tests/common.py
index aae0c0314..ca4139073 100644
--- a/mrp_multi_level/tests/common.py
+++ b/mrp_multi_level/tests/common.py
@@ -28,10 +28,14 @@ class TestMrpMultiLevelCommon(SavepointCase):
cls.fp_1 = cls.env.ref("mrp_multi_level.product_product_fp_1")
cls.fp_2 = cls.env.ref("mrp_multi_level.product_product_fp_2")
+ cls.fp_3 = cls.env.ref("mrp_multi_level.product_product_fp_3")
cls.sf_1 = cls.env.ref("mrp_multi_level.product_product_sf_1")
cls.sf_2 = cls.env.ref("mrp_multi_level.product_product_sf_2")
+ cls.sf_3 = cls.env.ref("mrp_multi_level.product_product_sf_3")
cls.pp_1 = cls.env.ref("mrp_multi_level.product_product_pp_1")
cls.pp_2 = cls.env.ref("mrp_multi_level.product_product_pp_2")
+ cls.pp_3 = cls.env.ref("mrp_multi_level.product_product_pp_3")
+ cls.pp_4 = cls.env.ref("mrp_multi_level.product_product_pp_4")
cls.product_4b = cls.env.ref("product.product_product_4b")
cls.av_11 = cls.env.ref("mrp_multi_level.product_product_av_11")
cls.av_12 = cls.env.ref("mrp_multi_level.product_product_av_12")
@@ -271,6 +275,19 @@ class TestMrpMultiLevelCommon(SavepointCase):
"location_dest_id": cls.customer_location.id,
},
),
+ (
+ 0,
+ 0,
+ {
+ "name": "Test move fp-3",
+ "product_id": cls.fp_3.id,
+ "date": date_move,
+ "product_uom": cls.fp_3.uom_id.id,
+ "product_uom_qty": 5,
+ "location_id": cls.stock_location.id,
+ "location_dest_id": cls.customer_location.id,
+ },
+ ),
(
0,
0,
diff --git a/mrp_multi_level/tests/test_mrp_multi_level.py b/mrp_multi_level/tests/test_mrp_multi_level.py
index 62b9912f3..1f874d664 100644
--- a/mrp_multi_level/tests/test_mrp_multi_level.py
+++ b/mrp_multi_level/tests/test_mrp_multi_level.py
@@ -389,3 +389,42 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon):
self.assertEqual(len(prod_uom_test_inventory_lines), 1)
self.assertEqual(prod_uom_test_inventory_lines.supply_qty, 12.0)
# Supply qty has to be 12 has a dozen of units are in a RFQ.
+
+ def test_16_phantom_comp_planning(self):
+ """
+ Phantom components will not appear in MRP Inventory or Planned Orders.
+ MRP Parameter will have 'phantom' supply method.
+ """
+ # SF-3
+ sf_3_line_1 = self.mrp_inventory_obj.search(
+ [("product_mrp_area_id.product_id", "=", self.sf_3.id)]
+ )
+ self.assertEqual(len(sf_3_line_1), 0)
+ 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")
+ # 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)
+ 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), 2)
+ # PP-4
+ pp_4_line_1 = self.mrp_inventory_obj.search(
+ [("product_mrp_area_id.product_id", "=", self.pp_4.id)]
+ )
+ self.assertEqual(len(pp_4_line_1), 1)
+ self.assertEqual(pp_4_line_1.demand_qty, 30.0)
+ pp_4_planned_orders = self.planned_order_obj.search(
+ [("product_mrp_area_id.product_id", "=", self.pp_4.id)]
+ )
+ self.assertEqual(len(pp_4_planned_orders), 1)
diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py
index 731230d81..6f38dd7ae 100644
--- a/mrp_multi_level/wizards/mrp_multi_level.py
+++ b/mrp_multi_level/wizards/mrp_multi_level.py
@@ -121,7 +121,15 @@ class MultiLevelMrp(models.TransientModel):
@api.model
def _prepare_mrp_move_data_bom_explosion(
- self, product, bomline, qty, mrp_date_demand_2, bom, name, planned_order
+ self,
+ product,
+ bomline,
+ qty,
+ mrp_date_demand_2,
+ bom,
+ name,
+ planned_order,
+ values=None,
):
product_mrp_area = self._get_product_mrp_area_from_product_and_area(
bomline.product_id, product.mrp_area_id
@@ -157,7 +165,7 @@ class MultiLevelMrp(models.TransientModel):
).replace(
"Demand Bom Explosion: Demand Bom Explosion: ", "Demand Bom Explosion: "
),
- "origin": planned_order.origin,
+ "origin": planned_order.origin if planned_order else values.get("origin"),
}
@api.model
@@ -182,7 +190,9 @@ class MultiLevelMrp(models.TransientModel):
return mrp_action_date, mrp_date_supply
@api.model
- def explode_action(self, product_mrp_area_id, mrp_action_date, name, qty, action):
+ def explode_action(
+ self, product_mrp_area_id, mrp_action_date, name, qty, action, values=None
+ ):
"""Explode requirements."""
mrp_date_demand = mrp_action_date
if mrp_date_demand < date.today():
@@ -221,6 +231,7 @@ class MultiLevelMrp(models.TransientModel):
bom,
name,
action,
+ values,
)
mrpmove_id2 = self.env["mrp.move"].create(move_data)
if hasattr(action, "mrp_move_down_ids"):
@@ -265,12 +276,20 @@ class MultiLevelMrp(models.TransientModel):
order_data = self._prepare_planned_order_data(
product_mrp_area_id, qty, mrp_date_supply, mrp_action_date, name, values
)
- planned_order = self.env["mrp.planned.order"].create(order_data)
+ # Do not create planned order for products that are Kits
+ planned_order = False
+ if not product_mrp_area_id.supply_method == "phantom":
+ planned_order = self.env["mrp.planned.order"].create(order_data)
qty_ordered = qty_ordered + qty
if product_mrp_area_id._to_be_exploded():
self.explode_action(
- product_mrp_area_id, mrp_action_date, name, qty, planned_order
+ product_mrp_area_id,
+ mrp_action_date,
+ name,
+ qty,
+ planned_order,
+ values,
)
values["qty_ordered"] = qty_ordered
@@ -768,8 +787,13 @@ class MultiLevelMrp(models.TransientModel):
for product_mrp_area in product_mrp_area_ids:
# Build the time-phased inventory
- if self._exclude_from_mrp(
- product_mrp_area.product_id, product_mrp_area.mrp_area_id
+ # Condition "supply_method == phantom" is not added in _exclude_from_mrp
+ # because this function is also used to filter the explosion.
+ if (
+ self._exclude_from_mrp(
+ product_mrp_area.product_id, product_mrp_area.mrp_area_id
+ )
+ or product_mrp_area.supply_method == "phantom"
):
continue
self._init_mrp_inventory(product_mrp_area)