[Fix] BOM replace timeout issue (#14782)

diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js
index a4b48af..e4b8a20 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js
@@ -22,6 +22,21 @@
 		frm.disable_save();
 	},
 
+	replace: function(frm) {
+		if (frm.doc.current_bom && frm.doc.new_bom) {
+			frappe.call({
+				method: "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.enqueue_replace_bom",
+				freeze: true,
+				args: {
+					args: {
+						"current_bom": frm.doc.current_bom,
+						"new_bom": frm.doc.new_bom
+					}
+				}
+			});
+		}
+	},
+
 	update_latest_price_in_all_boms: function() {
 		frappe.call({
 			method: "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.enqueue_update_cost",
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json
index ab63c0b..b348bb7 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json
@@ -123,7 +123,7 @@
    "label": "Replace", 
    "length": 0, 
    "no_copy": 0, 
-   "options": "replace_bom", 
+   "options": "", 
    "permlevel": 0, 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
@@ -208,7 +208,7 @@
  "issingle": 1, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2017-07-31 18:08:05.919276", 
+ "modified": "2018-07-02 16:17:09.014102", 
  "modified_by": "Administrator", 
  "module": "Manufacturing", 
  "name": "BOM Update Tool", 
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index ec948eb..04f9717 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -3,9 +3,10 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-import frappe
+import frappe, json
 from frappe.utils import cstr, flt
 from frappe import _
+from six import string_types
 from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order
 from frappe.model.document import Document
 
@@ -17,12 +18,14 @@
 		updated_bom = []
 		for bom in bom_list:
 			bom_obj = frappe.get_doc("BOM", bom)
+			bom_obj.get_doc_before_save()
 			updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom)
 			bom_obj.calculate_cost()
 			bom_obj.update_parent_cost()
 			bom_obj.db_update()
-
-		frappe.msgprint(_("BOM replaced"))
+			if (getattr(bom_obj.meta, 'track_changes', False)
+				and bom_obj._doc_before_save and not bom_obj.flags.ignore_version):
+				bom_obj.save_version()
 
 	def validate_bom(self):
 		if cstr(self.current_bom) == cstr(self.new_bom):
@@ -55,6 +58,14 @@
 		return bom_list
 
 @frappe.whitelist()
+def enqueue_replace_bom(args):
+	if isinstance(args, string_types):
+		args = json.loads(args)
+
+	frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.replace_bom", args=args)
+	frappe.msgprint(_("Queued for replacing the BOM. It may take a few minutes."))
+
+@frappe.whitelist()
 def enqueue_update_cost():
 	frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost")
 	frappe.msgprint(_("Queued for updating latest price in all Bill of Materials. It may take a few minutes."))
@@ -63,6 +74,14 @@
 	if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"):
 		update_cost()
 
+def replace_bom(args):
+	args = frappe._dict(args)
+
+	doc = frappe.get_doc("BOM Update Tool")
+	doc.current_bom = args.current_bom
+	doc.new_bom = args.new_bom
+	doc.replace_bom()
+
 def update_cost():
 	bom_list = get_boms_in_bottom_up_order()
 	for bom in bom_list: