feat: Production Planning Against Sales Order/Material Request/Work Order (#21763)

Co-authored-by: Marica <maricadsouza221197@gmail.com>
diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
index bbc4c99..ecd2dc9 100644
--- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
@@ -13,7 +13,7 @@
   {
    "hidden": 0,
    "label": "Reports",
-   "links": "[{\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Work Order Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Work Order Summary\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Analytics\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Analytics\"\n}, {\n\t\"dependencies\": [\"Quality Inspection\"],\n\t\"name\": \"Quality Inspection Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Quality Inspection\",\n\t\"label\": \"Quality Inspection Summary\"\n}, {\n\t\"dependencies\": [\"Downtime Entry\"],\n\t\"name\": \"Downtime Analysis\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Downtime Entry\",\n\t\"label\": \"Downtime Analysis\"\n}, {\n\t\"dependencies\": [\"Job Card\"],\n\t\"name\": \"Job Card Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Job Card\",\n\t\"label\": \"Job Card Summary\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Search\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Search\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Stock Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Stock Report\"\n}]"
+   "links": "[{\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Planning Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Planning Report\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Work Order Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Work Order Summary\"\n}, {\n\t\"dependencies\": [\"Quality Inspection\"],\n\t\"name\": \"Quality Inspection Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Quality Inspection\",\n\t\"label\": \"Quality Inspection Summary\"\n}, {\n\t\"dependencies\": [\"Downtime Entry\"],\n\t\"name\": \"Downtime Analysis\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Downtime Entry\",\n\t\"label\": \"Downtime Analysis\"\n}, {\n\t\"dependencies\": [\"Job Card\"],\n\t\"name\": \"Job Card Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Job Card\",\n\t\"label\": \"Job Card Summary\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Search\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Search\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Stock Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Stock Report\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Analytics\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Analytics\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Operations Time\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Operations Time\"\n}]"
   },
   {
    "hidden": 0,
@@ -46,7 +46,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Manufacturing",
- "modified": "2020-05-19 12:54:04.104444",
+ "modified": "2020-05-19 14:05:59.100891",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Manufacturing",
diff --git a/erpnext/manufacturing/report/bom_operations_time/__init__.py b/erpnext/manufacturing/report/bom_operations_time/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/__init__.py
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
new file mode 100644
index 0000000..7468e34
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
@@ -0,0 +1,9 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["BOM Operations Time"] = {
+	"filters": [
+
+	]
+};
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
new file mode 100644
index 0000000..665c5b9
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
@@ -0,0 +1,28 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-03-03 01:41:20.862521",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "",
+ "modified": "2020-03-03 01:41:20.862521",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "BOM Operations Time",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "BOM",
+ "report_name": "BOM Operations Time",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Manufacturing Manager"
+  },
+  {
+   "role": "Manufacturing User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
new file mode 100644
index 0000000..1279011
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
@@ -0,0 +1,112 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+	data = get_data(filters)
+	columns = get_columns(filters)
+	return columns, data
+
+def get_data(filters):
+	data = []
+
+	bom_data = []
+	for d in frappe.db.sql("""
+		SELECT
+			bom.name, bom.item, bom.item_name, bom.uom,
+			bomps.operation, bomps.workstation, bomps.time_in_mins
+		FROM `tabBOM` bom, `tabBOM Operation` bomps
+		WHERE
+			bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent
+		""", as_dict=1):
+		row = get_args()
+		if d.name not in bom_data:
+			bom_data.append(d.name)
+			row.update(d)
+		else:
+			row.update({
+				"operation": d.operation,
+				"workstation": d.workstation,
+				"time_in_mins": d.time_in_mins
+			})
+
+		data.append(row)
+
+	used_as_subassembly_items = get_bom_count(bom_data)
+
+	for d in data:
+		d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0)
+
+	return data
+
+def get_bom_count(bom_data):
+	data = frappe.get_all("BOM Item",
+		fields=["count(name) as count", "bom_no"],
+		filters= {"bom_no": ("in", bom_data)}, group_by = "bom_no")
+
+	bom_count = {}
+	for d in data:
+		bom_count.setdefault(d.bom_no, d.count)
+
+	return bom_count
+
+def get_args():
+	return frappe._dict({
+		"name": "",
+		"item": "",
+		"item_name": "",
+		"uom": ""
+	})
+
+def get_columns(filters):
+	return [{
+		"label": _("BOM ID"),
+		"options": "BOM",
+		"fieldname": "name",
+		"fieldtype": "Link",
+		"width": 140
+	}, {
+		"label": _("BOM Item Code"),
+		"options": "Item",
+		"fieldname": "item",
+		"fieldtype": "Link",
+		"width": 140
+	}, {
+		"label": _("Item Name"),
+		"fieldname": "item_name",
+		"fieldtype": "Data",
+		"width": 110
+	}, {
+		"label": _("UOM"),
+		"options": "UOM",
+		"fieldname": "uom",
+		"fieldtype": "Link",
+		"width": 140
+	}, {
+		"label": _("Operation"),
+		"options": "Operation",
+		"fieldname": "operation",
+		"fieldtype": "Link",
+		"width": 120
+	}, {
+		"label": _("Workstation"),
+		"options": "Workstation",
+		"fieldname": "workstation",
+		"fieldtype": "Link",
+		"width": 110
+	}, {
+		"label": _("Time (In Mins)"),
+		"fieldname": "time_in_mins",
+		"fieldtype": "Int",
+		"width": 140
+	}, {
+		"label": _("Subassembly BOM Count"),
+		"fieldname": "used_as_subassembly_items",
+		"fieldtype": "Int",
+		"width": 180
+	}]
+
+
diff --git a/erpnext/manufacturing/report/production_planning_report/__init__.py b/erpnext/manufacturing/report/production_planning_report/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/__init__.py
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.js b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js
new file mode 100644
index 0000000..675b8a1
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js
@@ -0,0 +1,111 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Production Planning Report"] = {
+	"filters": [
+		{
+			"fieldname":"company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"reqd": 1,
+			"default": frappe.defaults.get_user_default("Company")
+		},
+		{
+			"fieldname":"based_on",
+			"label": __("Based On"),
+			"fieldtype": "Select",
+			"options": ["Sales Order", "Material Request", "Work Order"],
+			"default": "Sales Order",
+			"reqd": 1,
+			on_change: function() {
+				let filters = frappe.query_report.filters;
+				let based_on = frappe.query_report.get_filter_value('based_on');
+				let options = {
+					"Sales Order": ["Delivery Date", "Total Amount"],
+					"Material Request": ["Required Date"],
+					"Work Order": ["Planned Start Date"]
+				}
+
+				filters.forEach(d => {
+					if (d.fieldname == "order_by") {
+						d.df.options = options[based_on];
+						d.set_input(d.df.options)
+					}
+				});
+
+				frappe.query_report.refresh();
+			}
+		},
+		{
+			"fieldname":"docnames",
+			"label": __("Document Name"),
+			"fieldtype": "MultiSelectList",
+			"options": "Sales Order",
+			"get_data": function(txt) {
+				if (!frappe.query_report.filters) return;
+
+				let based_on = frappe.query_report.get_filter_value('based_on');
+				if (!based_on) return;
+
+				return frappe.db.get_link_options(based_on, txt);
+			},
+			"get_query": function() {
+				var company = frappe.query_report.get_filter_value('company');
+				return {
+					filters: {
+						"docstatus": 1,
+						"company": company
+					}
+				};
+			}
+		},
+		{
+			"fieldname":"raw_material_warehouse",
+			"label": __("Raw Material Warehouse"),
+			"fieldtype": "Link",
+			"options": "Warehouse",
+			"depends_on": "eval: doc.based_on != 'Work Order'",
+			"get_query": function() {
+				var company = frappe.query_report.get_filter_value('company');
+				return {
+					filters: {
+						"company": company
+					}
+				};
+			}
+		},
+		{
+			"fieldname":"order_by",
+			"label": __("Order By"),
+			"fieldtype": "Select",
+			"options": ["Delivery Date", "Total Amount"],
+			"default": "Delivery Date"
+		},
+		{
+			"fieldname":"include_subassembly_raw_materials",
+			"label": __("Include Sub-assembly Raw Materials"),
+			"fieldtype": "Check",
+			"depends_on": "eval: doc.based_on != 'Work Order'",
+			"default": 0
+		},
+	],
+	"formatter": function(value, row, column, data, default_formatter) {
+		value = default_formatter(value, row, column, data);
+
+		if (column.fieldname == "production_item_name" && data && data.qty_to_manufacture > data.available_qty ) {
+			value = `<div style="color:red">${value}</div>`;
+		}
+
+		if (column.fieldname == "production_item" && !data.name ) {
+			value = "";
+		}
+
+		if (column.fieldname == "raw_material_name" && data && data.required_qty > data.allotted_qty ) {
+			value = `<div style="color:red">${value}</div>`;
+		}
+
+		return value;
+	},
+};
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.json b/erpnext/manufacturing/report/production_planning_report/production_planning_report.json
new file mode 100644
index 0000000..f37dad3
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-03-06 11:37:43.180095",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "",
+ "modified": "2020-03-06 11:38:05.789851",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Planning Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Order",
+ "report_name": "Production Planning Report",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Manufacturing User"
+  },
+  {
+   "role": "Stock User"
+  },
+  {
+   "role": "Manufacturing Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
new file mode 100644
index 0000000..b5e6c6f
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -0,0 +1,371 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
+
+# and bom_no is not null and bom_no !=''
+
+mapper = {
+	"Sales Order": {
+		"fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
+			stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse,
+			`tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """,
+		"filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty
+			and `tabSales Order`.per_delivered < 100.0"""
+	},
+	"Material Request": {
+		"fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
+			stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse,
+			`tabMaterial Request Item`.schedule_date """,
+		"filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100
+			and `tabMaterial Request`.material_request_type = 'Manufacture' """
+	},
+	"Work Order": {
+		"fields": """ production_item, item_name as production_item_name, planned_start_date,
+			stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """,
+		"filters": "docstatus = 1 and status not in ('Completed', 'Stopped')"
+	},
+}
+
+order_mapper = {
+	"Sales Order": {
+		"Delivery Date": "`tabSales Order Item`.delivery_date asc",
+		"Total Amount": "`tabSales Order`.base_grand_total desc"
+	},
+	"Material Request": {
+		"Required Date": "`tabMaterial Request Item`.schedule_date asc"
+	},
+	"Work Order": {
+		"Planned Start Date": "planned_start_date asc"
+	}
+}
+
+def execute(filters=None):
+	return ProductionPlanReport(filters).execute_report()
+
+class ProductionPlanReport(object):
+	def __init__(self, filters=None):
+		self.filters = frappe._dict(filters or {})
+		self.raw_materials_dict = {}
+		self.data = []
+
+	def execute_report(self):
+		self.get_open_orders()
+		self.get_raw_materials()
+		self.get_item_details()
+		self.get_bin_details()
+		self.get_purchase_details()
+		self.prepare_data()
+		self.get_columns()
+
+		return self.columns, self.data
+
+	def get_open_orders(self):
+		doctype = ("`tabWork Order`" if self.filters.based_on == "Work Order"
+			else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on))
+
+		filters = mapper.get(self.filters.based_on)["filters"]
+		filters = self.prepare_other_conditions(filters, self.filters.based_on)
+		order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by])
+
+		self.orders = frappe.db.sql(""" SELECT {fields} from {doctype}
+			WHERE {filters} {order_by}""".format(
+				doctype = doctype,
+				filters = filters,
+				order_by = order_by,
+				fields = mapper.get(self.filters.based_on)["fields"]
+			), tuple(self.filters.docnames), as_dict=1)
+
+	def prepare_other_conditions(self, filters, doctype):
+		if self.filters.docnames:
+			field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype)
+			filters += " and %s in (%s)" % (field, ','.join(['%s'] * len(self.filters.docnames)))
+
+		if doctype != "Work Order":
+			filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype)
+
+		if self.filters.company:
+			filters += " and `tab%s`.company = %s" %(doctype, frappe.db.escape(self.filters.company))
+
+		return filters
+
+	def get_raw_materials(self):
+		if not self.orders: return
+		self.warehouses = [d.warehouse for d in self.orders]
+		self.item_codes = [d.production_item for d in self.orders]
+
+		if self.filters.based_on == "Work Order":
+			work_orders = [d.name for d in self.orders]
+
+			raw_materials = frappe.get_all("Work Order Item",
+				fields=["parent", "item_code", "item_name as raw_material_name",
+					"source_warehouse as warehouse", "required_qty"],
+				filters = {"docstatus": 1, "parent": ("in", work_orders), "source_warehouse": ("!=", "")}) or []
+			self.warehouses.extend([d.source_warehouse for d in raw_materials])
+
+		else:
+			bom_nos = []
+
+			for d in self.orders:
+				bom_no = d.bom_no or frappe.get_cached_value("Item", d.production_item, "default_bom")
+
+				if not d.bom_no:
+					d.bom_no = bom_no
+
+				bom_nos.append(bom_no)
+
+			bom_doctype = ("BOM Explosion Item"
+				if self.filters.include_subassembly_raw_materials else "BOM Item")
+
+			qty_field = ("qty_consumed_per_unit"
+				if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
+
+			raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
+					bom_item.item_name as raw_material_name, {0} as required_qty
+				FROM
+					`tabBOM` as bom, `tab{1}` as bom_item
+				WHERE
+					bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1
+			""".format(qty_field, bom_doctype, ','.join(["%s"] * len(bom_nos))), tuple(bom_nos), as_dict=1)
+
+		if not raw_materials: return
+
+		self.item_codes.extend([d.item_code for d in raw_materials])
+
+		for d in raw_materials:
+			if d.parent not in self.raw_materials_dict:
+				self.raw_materials_dict.setdefault(d.parent, [])
+
+			rows = self.raw_materials_dict[d.parent]
+			rows.append(d)
+
+	def get_item_details(self):
+		if not (self.orders and self.item_codes): return
+
+		self.item_details = {}
+		for d in frappe.get_all("Item Default", fields = ["parent", "default_warehouse"],
+			filters = {"company": self.filters.company, "parent": ("in", self.item_codes)}):
+			self.item_details[d.parent] = d
+
+	def get_bin_details(self):
+		if not (self.orders and self.raw_materials_dict): return
+
+		self.bin_details = {}
+		self.mrp_warehouses = []
+		if self.filters.raw_material_warehouse:
+			self.mrp_warehouses.extend(get_child_warehouses(self.filters.raw_material_warehouse))
+			self.warehouses.extend(self.mrp_warehouses)
+
+		for d in frappe.get_all("Bin",
+			fields=["warehouse", "item_code", "actual_qty", "ordered_qty", "projected_qty"],
+			filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)}):
+			key = (d.item_code, d.warehouse)
+			if key not in self.bin_details:
+				self.bin_details.setdefault(key, d)
+
+	def get_purchase_details(self):
+		if not (self.orders and self.raw_materials_dict): return
+
+		self.purchase_details = {}
+
+		for d in frappe.get_all("Purchase Order Item",
+			fields=["item_code", "min(schedule_date) as arrival_date", "qty as arrival_qty", "warehouse"],
+			filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)},
+			group_by = "item_code, warehouse"):
+			key = (d.item_code, d.warehouse)
+			if key not in self.purchase_details:
+				self.purchase_details.setdefault(key, d)
+
+	def prepare_data(self):
+		if not self.orders: return
+
+		for d in self.orders:
+			key = d.name if self.filters.based_on == "Work Order" else d.bom_no
+
+			if not self.raw_materials_dict.get(key): continue
+
+			bin_data = self.bin_details.get((d.production_item, d.warehouse)) or {}
+			d.update({
+				"for_warehouse": d.warehouse,
+				"available_qty": 0
+			})
+
+			if bin_data and bin_data.get("actual_qty") > 0 and d.qty_to_manufacture:
+				d.available_qty = (bin_data.get("actual_qty")
+					if (d.qty_to_manufacture > bin_data.get("actual_qty")) else d.qty_to_manufacture)
+
+				bin_data["actual_qty"] -= d.available_qty
+
+			self.update_raw_materials(d, key)
+
+	def update_raw_materials(self, data, key):
+		self.index = 0
+		self.raw_materials_dict.get(key)
+
+		warehouses = self.mrp_warehouses or []
+		for d in self.raw_materials_dict.get(key):
+			if self.filters.based_on != "Work Order":
+				d.required_qty = d.required_qty * data.qty_to_manufacture
+
+			if not warehouses:
+				warehouses = [data.warehouse]
+
+			if self.filters.based_on == "Work Order" and d.warehouse:
+				warehouses = [d.warehouse]
+			else:
+				item_details = self.item_details.get(d.item_code)
+				if item_details:
+					warehouses = [item_details["default_warehouse"]]
+
+			d.remaining_qty = d.required_qty
+			self.pick_materials_from_warehouses(d, data, warehouses)
+
+			if (d.remaining_qty and self.filters.raw_material_warehouse
+				and d.remaining_qty != d.required_qty):
+				row = self.get_args()
+				d.warehouse = self.filters.raw_material_warehouse
+				d.required_qty = d.remaining_qty
+				d.allotted_qty = 0
+				row.update(d)
+				self.data.append(row)
+
+	def pick_materials_from_warehouses(self, args, order_data, warehouses):
+		for index, warehouse in enumerate(warehouses):
+			if not args.remaining_qty: return
+
+			row = self.get_args()
+
+			key = (args.item_code, warehouse)
+			bin_data = self.bin_details.get(key)
+
+			if bin_data:
+				row.update(bin_data)
+
+			args.allotted_qty = 0
+			if bin_data and bin_data.get("actual_qty") > 0:
+				args.allotted_qty = (bin_data.get("actual_qty")
+					if (args.required_qty > bin_data.get("actual_qty")) else args.required_qty)
+
+				args.remaining_qty -= args.allotted_qty
+				bin_data["actual_qty"] -= args.allotted_qty
+
+			if ((self.mrp_warehouses and (args.allotted_qty or index == len(warehouses) - 1))
+				or not self.mrp_warehouses):
+				if not self.index:
+					row.update(order_data)
+					self.index += 1
+
+				args.warehouse = warehouse
+				row.update(args)
+				if self.purchase_details.get(key):
+					row.update(self.purchase_details.get(key))
+
+				self.data.append(row)
+
+	def get_args(self):
+		return frappe._dict({
+			"work_order": "",
+			"sales_order": "",
+			"production_item": "",
+			"production_item_name": "",
+			"qty_to_manufacture": "",
+			"produced_qty": ""
+		})
+
+	def get_columns(self):
+		based_on = self.filters.based_on
+
+		self.columns = [{
+			"label": _("ID"),
+			"options": based_on,
+			"fieldname": "name",
+			"fieldtype": "Link",
+			"width": 100
+		}, {
+			"label": _("Item Code"),
+			"fieldname": "production_item",
+			"fieldtype": "Link",
+			"options": "Item",
+			"width": 120
+		}, {
+			"label": _("Item Name"),
+			"fieldname": "production_item_name",
+			"fieldtype": "Data",
+			"width": 130
+		}, {
+			"label": _("Warehouse"),
+			"options": "Warehouse",
+			"fieldname": "for_warehouse",
+			"fieldtype": "Link",
+			"width": 100
+		}, {
+			"label": _("Order Qty"),
+			"fieldname": "qty_to_manufacture",
+			"fieldtype": "Float",
+			"width": 80
+		}, {
+			"label": _("Available"),
+			"fieldname": "available_qty",
+			"fieldtype": "Float",
+			"width": 80
+		}]
+
+		fieldname, fieldtype = "delivery_date", "Date"
+		if self.filters.based_on == "Sales Order" and self.filters.order_by == "Total Amount":
+			fieldname, fieldtype = "base_grand_total", "Currency"
+		elif self.filters.based_on == "Material Request":
+			fieldname = "schedule_date"
+		elif self.filters.based_on == "Work Order":
+			fieldname = "planned_start_date"
+
+		self.columns.append({
+			"label": _(self.filters.order_by),
+			"fieldname": fieldname,
+			"fieldtype": fieldtype,
+			"width": 100
+		})
+
+		self.columns.extend([{
+			"label": _("Raw Material Code"),
+			"fieldname": "item_code",
+			"fieldtype": "Link",
+			"options": "Item",
+			"width": 120
+		}, {
+			"label": _("Raw Material Name"),
+			"fieldname": "raw_material_name",
+			"fieldtype": "Data",
+			"width": 130
+		}, {
+			"label": _("Warehouse"),
+			"options": "Warehouse",
+			"fieldname": "warehouse",
+			"fieldtype": "Link",
+			"width": 110
+		}, {
+			"label": _("Required Qty"),
+			"fieldname": "required_qty",
+			"fieldtype": "Float",
+			"width": 100
+		}, {
+			"label": _("Allotted Qty"),
+			"fieldname": "allotted_qty",
+			"fieldtype": "Float",
+			"width": 100
+		}, {
+			"label": _("Expected Arrival Date"),
+			"fieldname": "arrival_date",
+			"fieldtype": "Date",
+			"width": 160
+		}, {
+			"label": _("Arrival Quantity"),
+			"fieldname": "arrival_qty",
+			"fieldtype": "Float",
+			"width": 140
+		}])
+
+def document_query(doctype, txt, searchfield, start, page_len, filters):
+	pass
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index dc88ffb..56186c4 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -688,3 +688,4 @@
 erpnext.patches.v12_0.update_price_list_currency_in_bom
 execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
 erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
+erpnext.patches.v12_0.update_bom_in_so_mr
diff --git a/erpnext/patches/v12_0/update_bom_in_so_mr.py b/erpnext/patches/v12_0/update_bom_in_so_mr.py
new file mode 100644
index 0000000..309ae4c
--- /dev/null
+++ b/erpnext/patches/v12_0/update_bom_in_so_mr.py
@@ -0,0 +1,19 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doc("stock", "doctype", "material_request_item")
+	frappe.reload_doc("selling", "doctype", "sales_order_item")
+
+	for doctype in ["Sales Order", "Material Request"]:
+		condition = " and child_doc.stock_qty > child_doc.produced_qty"
+		if doctype == "Material Request":
+			condition = " and doc.per_ordered < 100 and doc.material_request_type = 'Manufacture'"
+
+		frappe.db.sql(""" UPDATE `tab{doc}` as doc, `tab{doc} Item` as child_doc, tabItem as item
+			SET
+				child_doc.bom_no = item.default_bom
+			WHERE
+				child_doc.item_code = item.name and child_doc.docstatus < 2
+				and item.default_bom is not null and item.default_bom != '' {cond}
+		""".format(doc = doctype, cond = condition))
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 45a43c5..705dcb8 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -34,6 +34,15 @@
 				}
 			};
 		})
+
+		frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
+			var row = locals[cdt][cdn];
+			return {
+				filters: {
+					"item": row.item_code
+				}
+			}
+		});
 	},
 	refresh: function(frm) {
 		if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed'
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 73f233c..e593499 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -72,6 +72,8 @@
   "against_blanket_order",
   "blanket_order",
   "blanket_order_rate",
+  "manufacturing_section_section",
+  "bom_no",
   "planning_section",
   "projected_qty",
   "actual_qty",
@@ -212,6 +214,7 @@
    "fieldtype": "Link",
    "label": "UOM",
    "options": "UOM",
+   "print_hide": 0,
    "reqd": 1
   },
   {
@@ -764,12 +767,24 @@
    "fieldname": "against_blanket_order",
    "fieldtype": "Check",
    "label": "Against Blanket Order"
-  }
+  },
+  {
+    "fieldname": "bom_no",
+    "fieldtype": "Link",
+    "label": "BOM No",
+    "no_copy": 1,
+    "options": "BOM",
+    "print_hide": 1
+   },
+   {
+    "fieldname": "manufacturing_section_section",
+    "fieldtype": "Section Break",
+    "label": "Manufacturing Section"
+   }
  ],
  "idx": 1,
  "istable": 1,
- "links": [],
- "modified": "2020-03-05 14:20:28.085117",
+ "modified": "2020-05-15 18:13:43.006493",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order Item",
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index db8bffd..3562181 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -30,7 +30,16 @@
 			return {
 				filters: {'company': doc.company}
 			};
-		})
+		});
+
+		frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
+			var row = locals[cdt][cdn];
+			return {
+				filters: {
+					"item": row.item_code
+				}
+			}
+		});
 	},
 
 	onload: function(frm) {
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index df140ff..32bd4a0 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -53,6 +53,8 @@
   "dimension_col_break",
   "cost_center",
   "section_break_37",
+  "bom_no",
+  "section_break_46",
   "page_break"
  ],
  "fields": [
@@ -371,8 +373,10 @@
    "label": "Image"
   },
   {
+   "depends_on": "eval:parent.material_request_type == \"Manufacture\"",
    "fieldname": "section_break_37",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "Manufacturing"
   },
   {
    "fieldname": "received_qty",
@@ -428,12 +432,24 @@
    "fieldtype": "Link",
    "label": "Source Warehouse (Material Transfer)",
    "options": "Warehouse"
+  },
+  {
+   "fieldname": "bom_no",
+   "fieldtype": "Link",
+   "label": "BOM No",
+   "no_copy": 1,
+   "options": "BOM",
+   "print_hide": 1
+  },
+  {
+   "fieldname": "section_break_46",
+   "fieldtype": "Section Break"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-05-01 09:00:00.992835",
+ "modified": "2020-05-15 09:00:00.992835",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Material Request Item",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 11b6403..0ed3b27 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -305,7 +305,8 @@
 		"weight_uom":item.weight_uom,
 		"last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0,
 		"transaction_date": args.get("transaction_date"),
-		"against_blanket_order": args.get("against_blanket_order")
+		"against_blanket_order": args.get("against_blanket_order"),
+		"bom_no": item.get("default_bom")
 	})
 
 	if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):