feat: provision to skip available sub assembly items in the production plan
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index fdaa4a2..232f1cb 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -35,8 +35,12 @@
   "section_break_25",
   "prod_plan_references",
   "section_break_24",
-  "get_sub_assembly_items",
   "combine_sub_items",
+  "section_break_ucc4",
+  "skip_available_sub_assembly_item",
+  "column_break_igxl",
+  "get_sub_assembly_items",
+  "section_break_g4ip",
   "sub_assembly_items",
   "download_materials_request_plan_section_section",
   "download_materials_required",
@@ -351,12 +355,12 @@
   {
    "fieldname": "section_break_24",
    "fieldtype": "Section Break",
-   "hide_border": 1
+   "hide_border": 1,
+   "label": "Sub Assembly Items"
   },
   {
    "fieldname": "sub_assembly_items",
    "fieldtype": "Table",
-   "label": "Sub Assembly Items",
    "no_copy": 1,
    "options": "Production Plan Sub Assembly Item"
   },
@@ -392,13 +396,33 @@
    "fieldname": "download_materials_request_plan_section_section",
    "fieldtype": "Section Break",
    "label": "Download Materials Request Plan Section"
+  },
+  {
+   "default": "0",
+   "description": "System consider the projected quantity to check available or will be available sub-assembly items ",
+   "fieldname": "skip_available_sub_assembly_item",
+   "fieldtype": "Check",
+   "label": "Skip Available Sub Assembly Items"
+  },
+  {
+   "fieldname": "section_break_ucc4",
+   "fieldtype": "Column Break",
+   "hide_border": 1
+  },
+  {
+   "fieldname": "section_break_g4ip",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_igxl",
+   "fieldtype": "Column Break"
   }
  ],
  "icon": "fa fa-calendar",
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-03-31 10:30:48.118932",
+ "modified": "2023-05-22 23:36:31.770517",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index f9e68b9..e40539a 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -718,7 +718,9 @@
 				frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
 
 			bom_data = []
-			get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
+
+			warehouse = row.warehouse if self.skip_available_sub_assembly_item else None
+			get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse)
 			self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
 			sub_assembly_items_store.extend(bom_data)
 
@@ -894,7 +896,9 @@
 	build_csv_response(item_list, doc.name)
 
 
-def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1):
+def get_exploded_items(
+	item_details, company, bom_no, include_non_stock_items, planned_qty=1, doc=None
+):
 	bei = frappe.qb.DocType("BOM Explosion Item")
 	bom = frappe.qb.DocType("BOM")
 	item = frappe.qb.DocType("Item")
@@ -1271,6 +1275,12 @@
 	include_safety_stock = doc.get("include_safety_stock")
 
 	so_item_details = frappe._dict()
+
+	sub_assembly_items = {}
+	if doc.get("skip_available_sub_assembly_item"):
+		for d in doc.get("sub_assembly_items"):
+			sub_assembly_items.setdefault((d.get("production_item"), d.get("bom_no")), d.get("qty"))
+
 	for data in po_items:
 		if not data.get("include_exploded_items") and doc.get("sub_assembly_items"):
 			data["include_exploded_items"] = 1
@@ -1296,10 +1306,24 @@
 				frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get("idx")))
 
 			if bom_no:
-				if data.get("include_exploded_items") and include_subcontracted_items:
+				if (
+					data.get("include_exploded_items")
+					and doc.get("sub_assembly_items")
+					and doc.get("skip_available_sub_assembly_item")
+				):
+					item_details = get_raw_materials_of_sub_assembly_items(
+						item_details,
+						company,
+						bom_no,
+						include_non_stock_items,
+						sub_assembly_items,
+						planned_qty=planned_qty,
+					)
+
+				elif data.get("include_exploded_items") and include_subcontracted_items:
 					# fetch exploded items from BOM
 					item_details = get_exploded_items(
-						item_details, company, bom_no, include_non_stock_items, planned_qty=planned_qty
+						item_details, company, bom_no, include_non_stock_items, planned_qty=planned_qty, doc=doc
 					)
 				else:
 					item_details = get_subitems(
@@ -1456,12 +1480,22 @@
 	}
 
 
-def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
+def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, company, warehouse=None, indent=0):
 	data = get_bom_children(parent=bom_no)
 	for d in data:
 		if d.expandable:
 			parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
 			stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
+
+			if warehouse:
+				bin_dict = get_bin_details(d, company, for_warehouse=warehouse)
+
+				if bin_dict and bin_dict[0].projected_qty > 0:
+					if bin_dict[0].projected_qty > stock_qty:
+						continue
+					else:
+						stock_qty = stock_qty - bin_dict[0].projected_qty
+
 			bom_data.append(
 				frappe._dict(
 					{
@@ -1481,7 +1515,7 @@
 			)
 
 			if d.value:
-				get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent + 1)
+				get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1)
 
 
 def set_default_warehouses(row, default_warehouses):
@@ -1519,3 +1553,68 @@
 	)
 
 	return reserved_qty_for_production_plan - reserved_qty_for_production
+
+
+def get_raw_materials_of_sub_assembly_items(
+	item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
+):
+
+	bei = frappe.qb.DocType("BOM Item")
+	bom = frappe.qb.DocType("BOM")
+	item = frappe.qb.DocType("Item")
+	item_default = frappe.qb.DocType("Item Default")
+	item_uom = frappe.qb.DocType("UOM Conversion Detail")
+
+	items = (
+		frappe.qb.from_(bei)
+		.join(bom)
+		.on(bom.name == bei.parent)
+		.join(item)
+		.on(item.name == bei.item_code)
+		.left_join(item_default)
+		.on((item_default.parent == item.name) & (item_default.company == company))
+		.left_join(item_uom)
+		.on((item.name == item_uom.parent) & (item_uom.uom == item.purchase_uom))
+		.select(
+			(IfNull(Sum(bei.stock_qty / IfNull(bom.quantity, 1)), 0) * planned_qty).as_("qty"),
+			item.item_name,
+			item.name.as_("item_code"),
+			bei.description,
+			bei.stock_uom,
+			bei.bom_no,
+			item.min_order_qty,
+			bei.source_warehouse,
+			item.default_material_request_type,
+			item.min_order_qty,
+			item_default.default_warehouse,
+			item.purchase_uom,
+			item_uom.conversion_factor,
+			item.safety_stock,
+		)
+		.where(
+			(bei.docstatus == 1)
+			& (bom.name == bom_no)
+			& (item.is_stock_item.isin([0, 1]) if include_non_stock_items else item.is_stock_item == 1)
+		)
+		.groupby(bei.item_code, bei.stock_uom)
+	).run(as_dict=True)
+
+	for item in items:
+		key = (item.item_code, item.bom_no)
+		if item.bom_no and key in sub_assembly_items:
+			planned_qty = flt(sub_assembly_items[key])
+			get_raw_materials_of_sub_assembly_items(
+				item_details,
+				company,
+				item.bom_no,
+				include_non_stock_items,
+				sub_assembly_items,
+				planned_qty=planned_qty,
+			)
+		else:
+			if not item.conversion_factor and item.purchase_uom:
+				item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom)
+
+			item_details.setdefault(item.get("item_code"), item)
+
+	return item_details
diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
index 4eb6bf6..fde0404 100644
--- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
@@ -28,7 +28,11 @@
   "uom",
   "stock_uom",
   "column_break_22",
-  "description"
+  "description",
+  "section_break_4rxf",
+  "actual_qty",
+  "column_break_xfhm",
+  "projected_qty"
  ],
  "fields": [
   {
@@ -183,12 +187,34 @@
    "fieldtype": "Datetime",
    "in_list_view": 1,
    "label": "Schedule Date"
+  },
+  {
+   "fieldname": "section_break_4rxf",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "actual_qty",
+   "fieldtype": "Float",
+   "label": "Actual Qty",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_xfhm",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "projected_qty",
+   "fieldtype": "Float",
+   "label": "Projected Qty",
+   "no_copy": 1,
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-11-28 13:50:15.116082",
+ "modified": "2023-05-22 17:52:34.708879",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan Sub Assembly Item",