refactor: validate_serial_numbers
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index e1ad2ab..a57a9f4 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -39,7 +39,6 @@
 from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
 from erpnext.setup.doctype.company.company import update_company_current_month_sales
 from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
-from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos
 
 form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
 
@@ -1642,48 +1641,9 @@
 		"""
 		validate serial number agains Delivery Note and Sales Invoice
 		"""
-		self.set_serial_no_against_delivery_note()
-		self.validate_serial_against_delivery_note()
-
-	def set_serial_no_against_delivery_note(self):
 		for item in self.items:
-			if item.serial_no and item.delivery_note and item.qty != len(get_serial_nos(item.serial_no)):
-				item.serial_no = get_delivery_note_serial_no(item.item_code, item.qty, item.delivery_note)
-
-	def validate_serial_against_delivery_note(self):
-		"""
-		validate if the serial numbers in Sales Invoice Items are same as in
-		Delivery Note Item
-		"""
-
-		for item in self.items:
-			if not item.delivery_note or not item.dn_detail:
-				continue
-
-			serial_nos = frappe.db.get_value("Delivery Note Item", item.dn_detail, "serial_no") or ""
-			dn_serial_nos = set(get_serial_nos(serial_nos))
-
-			serial_nos = item.serial_no or ""
-			si_serial_nos = set(get_serial_nos(serial_nos))
-			serial_no_diff = si_serial_nos - dn_serial_nos
-
-			if serial_no_diff:
-				dn_link = frappe.utils.get_link_to_form("Delivery Note", item.delivery_note)
-				serial_no_msg = ", ".join(frappe.bold(d) for d in serial_no_diff)
-
-				msg = _("Row #{0}: The following Serial Nos are not present in Delivery Note {1}:").format(
-					item.idx, dn_link
-				)
-				msg += " " + serial_no_msg
-
-				frappe.throw(msg=msg, title=_("Serial Nos Mismatch"))
-
-			if item.serial_no and cint(item.qty) != len(si_serial_nos):
-				frappe.throw(
-					_("Row #{0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
-						item.idx, item.qty, item.item_code, len(si_serial_nos)
-					)
-				)
+			item.set_serial_no_against_delivery_note()
+			item.validate_serial_against_delivery_note()
 
 	def update_project(self):
 		if self.project:
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py
index cf18529..cd235bf 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py
@@ -5,8 +5,10 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
+from frappe.utils.data import cint
 
 from erpnext.assets.doctype.asset.depreciation import get_disposal_account_and_cost_center
+from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos
 
 
 class SalesInvoiceItem(Document):
@@ -124,3 +126,39 @@
 		self.income_account = disposal_account
 		if not self.cost_center:
 			self.cost_center = depreciation_cost_center
+
+	def set_serial_no_against_delivery_note(self):
+		"""Set serial no based on delivery note."""
+		if self.serial_no and self.delivery_note and self.qty != len(get_serial_nos(self.serial_no)):
+			self.serial_no = get_delivery_note_serial_no(self.item_code, self.qty, self.delivery_note)
+
+	def validate_serial_against_delivery_note(self):
+		"""Ensure the serial numbers in this Sales Invoice Item are same as in the linked Delivery Note."""
+		if not self.delivery_note or not self.dn_detail:
+			return
+
+		serial_nos = frappe.db.get_value("Delivery Note Item", self.dn_detail, "serial_no") or ""
+		dn_serial_nos = set(get_serial_nos(serial_nos))
+
+		serial_nos = self.serial_no or ""
+		si_serial_nos = set(get_serial_nos(serial_nos))
+		serial_no_diff = si_serial_nos - dn_serial_nos
+
+		if serial_no_diff:
+			dn_link = frappe.utils.get_link_to_form("Delivery Note", self.delivery_note)
+			msg = (
+				_("Row #{0}: The following serial numbers are not present in Delivery Note {1}:").format(
+					self.idx, dn_link
+				)
+				+ " "
+				+ ", ".join(frappe.bold(d) for d in serial_no_diff)
+			)
+
+			frappe.throw(msg=msg, title=_("Serial Nos Mismatch"))
+
+		if self.serial_no and cint(self.qty) != len(si_serial_nos):
+			frappe.throw(
+				_(
+					"Row #{0}: {1} serial numbers are required for Item {2}. You have provided {3} serial numbers."
+				).format(self.idx, self.qty, self.item_code, len(si_serial_nos))
+			)