fix: serial no material transfer performance issue (#20747)

diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index d34f420..64d4c6c 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -205,6 +205,7 @@
 
 def validate_serial_no(sle, item_det):
 	serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else []
+	validate_material_transfer_entry(sle)
 
 	if item_det.has_serial_no==0:
 		if serial_nos:
@@ -224,7 +225,9 @@
 
 			for serial_no in serial_nos:
 				if frappe.db.exists("Serial No", serial_no):
-					sr = frappe.get_doc("Serial No", serial_no)
+					sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
+						"delivery_document_no", "delivery_document_type", "warehouse",
+						"purchase_document_no", "company"], as_dict=1)
 
 					if sr.item_code!=sle.item_code:
 						if not allow_serial_nos_with_different_item(serial_no, sle):
@@ -305,6 +308,19 @@
 				frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
 					.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse))
 
+def validate_material_transfer_entry(sle_doc):
+	sle_doc.update({
+		"skip_update_serial_no": False,
+		"skip_serial_no_validaiton": False
+	})
+
+	if (sle_doc.voucher_type == "Stock Entry" and sle_doc.is_cancelled == "No" and
+		frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"):
+		if sle_doc.actual_qty < 0:
+			sle_doc.skip_update_serial_no = True
+		else:
+			sle_doc.skip_serial_no_validaiton = True
+
 def validate_so_serial_no(sr, sales_order,):
 	if not sr.sales_order or sr.sales_order!= sales_order:
 		frappe.throw(_("""Sales Order {0} has reservation for item {1}, you can
@@ -312,7 +328,8 @@
 		be delivered""").format(sales_order, sr.item_code, sr.name))
 
 def has_duplicate_serial_no(sn, sle):
-	if sn.warehouse and sle.voucher_type != 'Stock Reconciliation':
+	if (sn.warehouse and not sle.skip_serial_no_validaiton
+		and sle.voucher_type != 'Stock Reconciliation'):
 		return True
 
 	if sn.company != sle.company:
@@ -337,7 +354,7 @@
 	"""
 	allow_serial_nos = False
 	if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0:
-		stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no)
+		stock_entry = frappe.get_cached_doc("Stock Entry", sle.voucher_no)
 		if stock_entry.purpose in ("Repack", "Manufacture"):
 			for d in stock_entry.get("items"):
 				if d.serial_no and (d.s_warehouse if sle.is_cancelled=="No" else d.t_warehouse):
@@ -348,6 +365,7 @@
 	return allow_serial_nos
 
 def update_serial_nos(sle, item_det):
+	if sle.skip_update_serial_no: return
 	if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \
 			and item_det.has_serial_no == 1 and item_det.serial_no_series:
 		serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty)
@@ -369,22 +387,16 @@
 	voucher_type = args.get('voucher_type')
 	item_code = args.get('item_code')
 	for serial_no in serial_nos:
+		is_new = False
 		if frappe.db.exists("Serial No", serial_no):
-			sr = frappe.get_doc("Serial No", serial_no)
-			sr.via_stock_ledger = True
-			sr.item_code = item_code
-			sr.warehouse = args.get('warehouse') if args.get('actual_qty', 0) > 0 else None
-			sr.batch_no = args.get('batch_no')
-			sr.location = args.get('location')
-			sr.company = args.get('company')
-			sr.supplier = args.get('supplier')
-			if sr.sales_order and voucher_type == "Stock Entry" \
-				and not args.get('actual_qty', 0) > 0:
-				sr.sales_order = None
-			sr.update_serial_no_reference()
-			sr.save(ignore_permissions=True)
+			sr = frappe.get_cached_doc("Serial No", serial_no)
 		elif args.get('actual_qty', 0) > 0:
-			created_numbers.append(make_serial_no(serial_no, args))
+			sr = frappe.new_doc("Serial No")
+			is_new = True
+
+		sr = update_args_for_serial_no(sr, serial_no, args, is_new=is_new)
+		if is_new:
+			created_numbers.append(sr.name)
 
 	form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers))
 
@@ -419,20 +431,34 @@
 	return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
 		if s.strip()]
 
-def make_serial_no(serial_no, args):
-	sr = frappe.new_doc("Serial No")
-	sr.serial_no = serial_no
-	sr.item_code = args.get('item_code')
-	sr.company = args.get('company')
-	sr.batch_no = args.get('batch_no')
-	sr.via_stock_ledger = args.get('via_stock_ledger') or True
-	sr.warehouse = args.get('warehouse')
+def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
+	serial_no_doc.update({
+		"item_code": args.get("item_code"),
+		"company": args.get("company"),
+		"batch_no": args.get("batch_no"),
+		"via_stock_ledger": args.get("via_stock_ledger") or True,
+		"supplier": args.get("supplier"),
+		"location": args.get("location"),
+		"warehouse": (args.get("warehouse")
+			if args.get("actual_qty", 0) > 0 else None)
+	})
 
-	sr.validate_item()
-	sr.update_serial_no_reference(serial_no)
-	sr.db_insert()
+	if is_new:
+		serial_no_doc.serial_no = serial_no
 
-	return sr.name
+	if (serial_no_doc.sales_order and args.get("voucher_type") == "Stock Entry"
+		and not args.get("actual_qty", 0) > 0):
+		serial_no_doc.sales_order = None
+
+	serial_no_doc.validate_item()
+	serial_no_doc.update_serial_no_reference(serial_no)
+
+	if is_new:
+		serial_no_doc.db_insert()
+	else:
+		serial_no_doc.db_update()
+
+	return serial_no_doc
 
 def update_serial_nos_after_submit(controller, parentfield):
 	stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no, actual_qty, warehouse
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
index c9eba71..c03eb79 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
@@ -240,6 +240,7 @@
    "options": "Company",
    "print_width": "150px",
    "read_only": 1,
+   "search_index": 1,
    "width": "150px"
   },
   {
@@ -274,7 +275,7 @@
  "icon": "fa fa-list",
  "idx": 1,
  "in_create": 1,
- "modified": "2019-11-27 12:17:31.522675",
+ "modified": "2020-02-25 22:53:33.504681",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Ledger Entry",
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 2c6c953..f3381c7 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -136,7 +136,7 @@
 		bin_obj.flags.ignore_permissions = 1
 		bin_obj.insert()
 	else:
-		bin_obj = frappe.get_doc('Bin', bin)
+		bin_obj = frappe.get_cached_doc('Bin', bin)
 	bin_obj.flags.ignore_permissions = True
 	return bin_obj