feat: provision to exclude exploded items in the BOM (#29450)

diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 6d35d65..fc3b971 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -331,7 +331,7 @@
 			});
 		});
 
-		if (has_template_rm) {
+		if (has_template_rm && has_template_rm.length) {
 			dialog.fields_dict.items.grid.refresh();
 		}
 	},
@@ -467,7 +467,8 @@
 				"uom": d.uom,
 				"stock_uom": d.stock_uom,
 				"conversion_factor": d.conversion_factor,
-				"sourced_by_supplier": d.sourced_by_supplier
+				"sourced_by_supplier": d.sourced_by_supplier,
+				"do_not_explode": d.do_not_explode
 			},
 			callback: function(r) {
 				d = locals[cdt][cdn];
@@ -640,6 +641,13 @@
 	});
 });
 
+frappe.ui.form.on("BOM Item", {
+	do_not_explode: function(frm, cdt, cdn) {
+		get_bom_material_detail(frm.doc, cdt, cdn, false);
+	}
+})
+
+
 frappe.ui.form.on("BOM Item", "qty", function(frm, cdt, cdn) {
 	var d = locals[cdt][cdn];
 	d.stock_qty = d.qty * d.conversion_factor;
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 5a60fb7..d953697 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -203,6 +203,10 @@
 		for item in self.get("items"):
 			self.validate_bom_currency(item)
 
+			item.bom_no = ''
+			if not item.do_not_explode:
+				item.bom_no = item.bom_no
+
 			ret = self.get_bom_material_detail({
 				"company": self.company,
 				"item_code": item.item_code,
@@ -214,8 +218,10 @@
 				"uom": item.uom,
 				"stock_uom": item.stock_uom,
 				"conversion_factor": item.conversion_factor,
-				"sourced_by_supplier": item.sourced_by_supplier
+				"sourced_by_supplier": item.sourced_by_supplier,
+				"do_not_explode": item.do_not_explode
 			})
+
 			for r in ret:
 				if not item.get(r):
 					item.set(r, ret[r])
@@ -267,6 +273,9 @@
 			 'sourced_by_supplier'		: args.get('sourced_by_supplier', 0)
 		}
 
+		if args.get('do_not_explode'):
+			ret_item['bom_no'] = ''
+
 		return ret_item
 
 	def validate_bom_currency(self, item):
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 178d92c..7d90933 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -385,6 +385,23 @@
 		self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results")
 		self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
 
+	def test_exclude_exploded_items_from_bom(self):
+		bom_no = get_default_bom()
+		new_bom = frappe.copy_doc(frappe.get_doc('BOM', bom_no))
+		for row in new_bom.items:
+			if row.item_code == '_Test Item Home Desktop Manufactured':
+				self.assertTrue(row.bom_no)
+				row.do_not_explode = True
+
+		new_bom.docstatus = 0
+		new_bom.save()
+		new_bom.load_from_db()
+
+		for row in new_bom.items:
+			if row.item_code == '_Test Item Home Desktop Manufactured' and row.do_not_explode:
+				self.assertFalse(row.bom_no)
+
+		new_bom.delete()
 
 def get_default_bom(item_code="_Test FG Item 2"):
 	return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index 4c9877f..3406215 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -10,6 +10,7 @@
   "item_name",
   "operation",
   "column_break_3",
+  "do_not_explode",
   "bom_no",
   "source_warehouse",
   "allow_alternative_item",
@@ -73,6 +74,7 @@
    "fieldtype": "Column Break"
   },
   {
+   "depends_on": "eval:!doc.do_not_explode",
    "fieldname": "bom_no",
    "fieldtype": "Link",
    "in_filter": 1,
@@ -284,18 +286,25 @@
    "fieldname": "sourced_by_supplier",
    "fieldtype": "Check",
    "label": "Sourced by Supplier"
+  },
+  {
+   "default": "0",
+   "fieldname": "do_not_explode",
+   "fieldtype": "Check",
+   "label": "Do Not Explode"
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-10-08 14:19:37.563300",
+ "modified": "2022-01-24 16:57:57.020232",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Item",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file