fix(patch): serial no whitespace trimming

old data can contain trailing/leading whitespace which doesn't work well
with code to find last SLE for serial no.
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 99741eb..c5e4f7e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -279,6 +279,7 @@
 erpnext.patches.v13_0.update_recipient_email_digest
 erpnext.patches.v13_0.shopify_deprecation_warning
 erpnext.patches.v13_0.remove_bad_selling_defaults
+erpnext.patches.v13_0.trim_whitespace_from_serial_nos
 erpnext.patches.v13_0.migrate_stripe_api
 erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
 erpnext.patches.v13_0.einvoicing_deprecation_warning
diff --git a/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py
new file mode 100644
index 0000000..4f11255
--- /dev/null
+++ b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py
@@ -0,0 +1,61 @@
+import frappe
+
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+
+def execute():
+	broken_sles = frappe.db.sql("""
+			select name, serial_no
+			from `tabStock Ledger Entry`
+			where
+				is_cancelled = 0
+				and (serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s)
+			""",
+			(
+				" %",    # leading whitespace
+				"% ",    # trailing whitespace
+				"%\n %", # leading whitespace on newline
+				"% \n%", # trailing whitespace on newline
+			),
+			as_dict=True,
+		)
+
+	frappe.db.MAX_WRITES_PER_TRANSACTION += len(broken_sles)
+
+	if not broken_sles:
+		return
+
+	broken_serial_nos = set()
+
+	for sle in broken_sles:
+		serial_no_list = get_serial_nos(sle.serial_no)
+		correct_sr_no = "\n".join(serial_no_list)
+
+		if correct_sr_no == sle.serial_no:
+			continue
+
+		frappe.db.set_value("Stock Ledger Entry", sle.name, "serial_no", correct_sr_no, update_modified=False)
+		broken_serial_nos.update(serial_no_list)
+
+	if not broken_serial_nos:
+		return
+
+	broken_sr_no_records = [sr[0] for sr in frappe.db.sql("""
+							select name
+							from `tabSerial No`
+							where status='Active'
+								and coalesce(purchase_document_type, '') = ''
+								and name in %s """, (list(broken_serial_nos),)
+							)]
+
+	frappe.db.MAX_WRITES_PER_TRANSACTION += len(broken_sr_no_records)
+
+	patch_savepoint = "serial_no_patch"
+	for serial_no in broken_sr_no_records:
+		try:
+			frappe.db.savepoint(patch_savepoint)
+			sn = frappe.get_doc("Serial No", serial_no)
+			sn.update_serial_no_reference()
+			sn.db_update()
+		except Exception:
+			frappe.db.rollback(save_point=patch_savepoint)