refactor: serial and batch reposting
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 3f77869..c75d57f 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -70,6 +70,7 @@
   "target_warehouse",
   "quality_inspection",
   "col_break4",
+  "allow_zero_valuation_rate",
   "against_sales_order",
   "so_detail",
   "against_sales_invoice",
@@ -79,6 +80,10 @@
   "section_break_40",
   "pick_serial_and_batch",
   "serial_and_batch_bundle",
+  "column_break_eaoe",
+  "serial_no",
+  "batch_no",
+  "available_qty_section",
   "actual_batch_qty",
   "actual_qty",
   "installed_qty",
@@ -88,7 +93,6 @@
   "received_qty",
   "accounting_details_section",
   "expense_account",
-  "allow_zero_valuation_rate",
   "column_break_71",
   "internal_transfer_section",
   "material_request",
@@ -505,7 +509,8 @@
   },
   {
    "fieldname": "section_break_40",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "Serial and Batch No"
   },
   {
    "allow_on_submit": 1,
@@ -847,19 +852,44 @@
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
-   "options": "Serial and Batch Bundle"
+   "no_copy": 1,
+   "options": "Serial and Batch Bundle",
+   "print_hide": 1
   },
   {
    "fieldname": "pick_serial_and_batch",
    "fieldtype": "Button",
    "label": "Pick Serial / Batch No"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "available_qty_section",
+   "fieldtype": "Section Break",
+   "label": "Available Qty"
+  },
+  {
+   "fieldname": "column_break_eaoe",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "serial_no",
+   "fieldtype": "Text",
+   "label": "Serial No",
+   "read_only": 1
+  },
+  {
+   "fieldname": "batch_no",
+   "fieldtype": "Link",
+   "label": "Batch No",
+   "options": "Batch",
+   "read_only": 1
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-05-01 21:05:14.175640",
+ "modified": "2023-05-02 21:05:14.175640",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 900fb75..f779893 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -79,6 +79,7 @@
   "purchase_order",
   "purchase_invoice",
   "column_break_40",
+  "allow_zero_valuation_rate",
   "is_fixed_asset",
   "asset_location",
   "asset_category",
@@ -93,8 +94,12 @@
   "section_break_45",
   "update_serial_batch_bundle",
   "serial_and_batch_bundle",
+  "rejected_serial_and_batch_bundle",
   "col_break5",
-  "allow_zero_valuation_rate",
+  "serial_no",
+  "rejected_serial_no",
+  "batch_no",
+  "subcontract_bom_section",
   "include_exploded_items",
   "bom",
   "item_weight_details",
@@ -998,12 +1003,43 @@
    "fieldname": "update_serial_batch_bundle",
    "fieldtype": "Button",
    "label": "Add Serial / Batch No"
+  },
+  {
+   "depends_on": "eval:parent.is_old_subcontracting_flow",
+   "fieldname": "subcontract_bom_section",
+   "fieldtype": "Section Break",
+   "label": "Subcontract BOM"
+  },
+  {
+   "fieldname": "serial_no",
+   "fieldtype": "Text",
+   "label": "Serial No",
+   "read_only": 1
+  },
+  {
+   "fieldname": "rejected_serial_no",
+   "fieldtype": "Text",
+   "label": "Rejected Serial No",
+   "read_only": 1
+  },
+  {
+   "fieldname": "batch_no",
+   "fieldtype": "Link",
+   "label": "Batch No",
+   "options": "Batch",
+   "read_only": 1
+  },
+  {
+   "fieldname": "rejected_serial_and_batch_bundle",
+   "fieldtype": "Link",
+   "label": "Rejected Serial and Batch Bundle",
+   "options": "Serial and Batch Bundle"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-02-28 16:43:04.470104",
+ "modified": "2023-03-03 12:45:03.087766",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
index cfe35d7..4148946 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
@@ -7,8 +7,8 @@
  "field_order": [
   "item_details_tab",
   "company",
-  "item_group",
   "warehouse",
+  "type_of_transaction",
   "column_break_4",
   "item_code",
   "item_name",
@@ -18,6 +18,7 @@
   "ledgers",
   "quantity_and_rate_section",
   "total_qty",
+  "item_group",
   "column_break_13",
   "avg_rate",
   "total_amount",
@@ -46,6 +47,7 @@
    "fetch_from": "item_code.item_group",
    "fieldname": "item_group",
    "fieldtype": "Link",
+   "hidden": 1,
    "label": "Item Group",
    "options": "Item Group"
   },
@@ -171,12 +173,19 @@
    "label": "Warehouse",
    "options": "Warehouse",
    "reqd": 1
+  },
+  {
+   "fieldname": "type_of_transaction",
+   "fieldtype": "Select",
+   "label": "Type of Transaction",
+   "options": "\nInward\nOutward",
+   "reqd": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-10 11:32:09.018760",
+ "modified": "2023-03-03 16:18:53.709069",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial and Batch Bundle",
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 1c9dc15..0f8f6d2 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -267,7 +267,11 @@
 			serial_batch_table.qty,
 			serial_batch_table.incoming_rate,
 		)
-		.where((sle_table.item_code == kwargs.item_code) & (sle_table.warehouse == kwargs.warehouse))
+		.where(
+			(sle_table.item_code == kwargs.item_code)
+			& (sle_table.warehouse == kwargs.warehouse)
+			& (serial_batch_table.is_outward == 0)
+		)
 	)
 
 	if kwargs.serial_nos:
diff --git a/erpnext/stock/doctype/serial_and_batch_ledger/serial_and_batch_ledger.json b/erpnext/stock/doctype/serial_and_batch_ledger/serial_and_batch_ledger.json
index 65eaa03..d993225 100644
--- a/erpnext/stock/doctype/serial_and_batch_ledger/serial_and_batch_ledger.json
+++ b/erpnext/stock/doctype/serial_and_batch_ledger/serial_and_batch_ledger.json
@@ -15,7 +15,8 @@
   "incoming_rate",
   "column_break_8",
   "outgoing_rate",
-  "stock_value_difference"
+  "stock_value_difference",
+  "is_outward"
  ],
  "fields": [
   {
@@ -93,12 +94,19 @@
    "label": "Change in Stock Value",
    "no_copy": 1,
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "is_outward",
+   "fieldtype": "Check",
+   "label": "Is Outward",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-01-10 12:55:57.368650",
+ "modified": "2023-03-03 16:52:26.039613",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial and Batch Ledger",
diff --git a/erpnext/stock/doctype/serial_and_batch_no_bundle/__init__.py b/erpnext/stock/doctype/serial_and_batch_no_bundle/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/doctype/serial_and_batch_no_bundle/__init__.py
diff --git a/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.js b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.js
new file mode 100644
index 0000000..c36abd6
--- /dev/null
+++ b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+// frappe.ui.form.on("Serial and Batch No Bundle", {
+// 	refresh(frm) {
+
+// 	},
+// });
diff --git a/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.json b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.json
new file mode 100644
index 0000000..ec33156
--- /dev/null
+++ b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.json
@@ -0,0 +1,176 @@
+{
+ "actions": [],
+ "creation": "2022-09-29 14:56:38.338267",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "item_details_tab",
+  "company",
+  "item_group",
+  "has_serial_no",
+  "column_break_4",
+  "item_code",
+  "item_name",
+  "has_batch_no",
+  "serial_no_and_batch_no_tab",
+  "ledgers",
+  "qty",
+  "reference_tab",
+  "voucher_type",
+  "voucher_no",
+  "posting_date",
+  "posting_time",
+  "is_cancelled",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "item_details_tab",
+   "fieldtype": "Tab Break",
+   "label": "Item Details"
+  },
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "item_code.item_group",
+   "fieldname": "item_group",
+   "fieldtype": "Link",
+   "label": "Item Group",
+   "options": "Item Group"
+  },
+  {
+   "default": "0",
+   "fetch_from": "item_code.has_serial_no",
+   "fieldname": "has_serial_no",
+   "fieldtype": "Check",
+   "label": "Has Serial No",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Item Code",
+   "options": "Item",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "item_code.item_name",
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "label": "Item Name"
+  },
+  {
+   "default": "0",
+   "fetch_from": "item_code.has_batch_no",
+   "fieldname": "has_batch_no",
+   "fieldtype": "Check",
+   "label": "Has Batch No",
+   "read_only": 1
+  },
+  {
+   "fieldname": "serial_no_and_batch_no_tab",
+   "fieldtype": "Section Break"
+  },
+  {
+   "allow_bulk_edit": 1,
+   "fieldname": "ledgers",
+   "fieldtype": "Table",
+   "label": "Serial No and Batch No Transaction",
+   "options": "Serial and Batch No Ledger",
+   "reqd": 1
+  },
+  {
+   "fieldname": "qty",
+   "fieldtype": "Float",
+   "label": "Total Qty",
+   "read_only": 1
+  },
+  {
+   "fieldname": "reference_tab",
+   "fieldtype": "Tab Break",
+   "label": "Reference"
+  },
+  {
+   "fieldname": "voucher_type",
+   "fieldtype": "Link",
+   "label": "Voucher Type",
+   "options": "DocType",
+   "reqd": 1
+  },
+  {
+   "fieldname": "voucher_no",
+   "fieldtype": "Dynamic Link",
+   "label": "Voucher No",
+   "options": "voucher_type"
+  },
+  {
+   "fieldname": "posting_date",
+   "fieldtype": "Date",
+   "label": "Posting Date",
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "is_cancelled",
+   "fieldtype": "Check",
+   "label": "Is Cancelled",
+   "read_only": 1
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Serial and Batch No Bundle",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "posting_time",
+   "fieldtype": "Time",
+   "label": "Posting Time",
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2023-03-05 17:38:51.871723",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Serial and Batch No Bundle",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "item_code"
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.py b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.py
new file mode 100644
index 0000000..46c0e5a
--- /dev/null
+++ b/erpnext/stock/doctype/serial_and_batch_no_bundle/serial_and_batch_no_bundle.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class SerialandBatchNoBundle(Document):
+	pass
diff --git a/erpnext/stock/doctype/serial_and_batch_no_bundle/test_serial_and_batch_no_bundle.py b/erpnext/stock/doctype/serial_and_batch_no_bundle/test_serial_and_batch_no_bundle.py
new file mode 100644
index 0000000..2d5b9d3
--- /dev/null
+++ b/erpnext/stock/doctype/serial_and_batch_no_bundle/test_serial_and_batch_no_bundle.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestSerialandBatchNoBundle(FrappeTestCase):
+	pass
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
new file mode 100644
index 0000000..f32b79d
--- /dev/null
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -0,0 +1,385 @@
+import frappe
+from frappe.model.naming import make_autoname
+from frappe.query_builder.functions import CombineDatetime, Sum
+from frappe.utils import cint, cstr, flt, now
+
+from erpnext.stock.valuation import round_off_if_near_zero
+
+
+class SerialBatchBundle:
+	def __init__(self, **kwargs):
+		for key, value in kwargs.iteritems():
+			setattr(self, key, value)
+
+		self.set_item_details()
+
+	def process_serial_and_batch_bundle(self):
+		if self.item_details.has_serial_no:
+			self.process_serial_no
+		elif self.item_details.has_batch_no:
+			self.process_batch_no
+
+	def set_item_details(self):
+		fields = [
+			"has_batch_no",
+			"has_serial_no",
+			"item_name",
+			"item_group",
+			"serial_no_series",
+			"create_new_batch",
+			"batch_number_series",
+		]
+
+		self.item_details = frappe.get_cached_value("Item", self.sle.item_code, fields, as_dict=1)
+
+	def process_serial_no(self):
+		if (
+			not self.sle.is_cancelled
+			and not self.sle.serial_and_batch_bundle
+			and self.sle.actual_qty > 0
+			and self.item_details.has_serial_no == 1
+			and self.item_details.serial_no_series
+		):
+			sr_nos = self.auto_create_serial_nos()
+			self.make_serial_no_bundle(sr_nos)
+
+	def auto_create_serial_nos(self):
+		sr_nos = []
+		serial_nos_details = []
+
+		for i in range(cint(self.sle.actual_qty)):
+			serial_no = make_autoname(self.item_details.serial_no_series, "Serial No")
+			sr_nos.append(serial_no)
+			serial_nos_details.append(
+				(
+					serial_no,
+					serial_no,
+					now(),
+					now(),
+					frappe.session.user,
+					frappe.session.user,
+					self.warehouse,
+					self.company,
+					self.item_code,
+					self.item_details.item_name,
+					self.item_details.description,
+				)
+			)
+
+		if serial_nos_details:
+			fields = [
+				"name",
+				"serial_no",
+				"creation",
+				"modified",
+				"owner",
+				"modified_by",
+				"warehouse",
+				"company",
+				"item_code",
+				"item_name",
+				"description",
+			]
+
+			frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
+
+		return sr_nos
+
+	def make_serial_no_bundle(self, serial_nos=None):
+		sn_doc = frappe.new_doc("Serial and Batch Bundle")
+		sn_doc.item_code = self.item_code
+		sn_doc.item_name = self.item_details.item_name
+		sn_doc.item_group = self.item_details.item_group
+		sn_doc.has_serial_no = self.item_details.has_serial_no
+		sn_doc.has_batch_no = self.item_details.has_batch_no
+		sn_doc.voucher_type = self.sle.voucher_type
+		sn_doc.voucher_no = self.sle.voucher_no
+		sn_doc.flags.ignore_mandatory = True
+		sn_doc.flags.ignore_validate = True
+		sn_doc.total_qty = self.sle.actual_qty
+		sn_doc.avg_rate = self.sle.incoming_rate
+		sn_doc.total_amount = flt(self.sle.actual_qty) * flt(self.sle.incoming_rate)
+		sn_doc.insert()
+
+		batch_no = ""
+		if self.item_details.has_batch_no:
+			batch_no = self.create_batch()
+
+		if serial_nos:
+			self.add_serial_no_to_bundle(sn_doc, serial_nos, batch_no)
+		elif self.item_details.has_batch_no:
+			self.add_batch_no_to_bundle(sn_doc, batch_no)
+			sn_doc.save()
+
+		sn_doc.load_from_db()
+		sn_doc.flags.ignore_validate = True
+		sn_doc.flags.ignore_mandatory = True
+
+		sn_doc.submit()
+
+		self.sle.serial_and_batch_bundle = sn_doc.name
+
+	def add_serial_no_to_bundle(self, sn_doc, serial_nos, batch_no=None):
+		ledgers = []
+
+		fields = [
+			"name",
+			"serial_no",
+			"batch_no",
+			"warehouse",
+			"item_code",
+			"qty",
+			"incoming_rate",
+			"parent",
+			"parenttype",
+			"parentfield",
+		]
+
+		for serial_no in serial_nos:
+			ledgers.append(
+				(
+					frappe.generate_hash("Serial and Batch Ledger", 10),
+					serial_no,
+					batch_no,
+					self.warehouse,
+					self.item_details.item_code,
+					1,
+					self.sle.incoming_rate,
+					sn_doc.name,
+					sn_doc.doctype,
+					"ledgers",
+				)
+			)
+
+		frappe.db.bulk_insert("Serial and Batch Ledger", fields=fields, values=set(ledgers))
+
+	def add_batch_no_to_bundle(self, sn_doc, batch_no):
+		sn_doc.append(
+			"ledgers",
+			{
+				"batch_no": batch_no,
+				"qty": self.sle.actual_qty,
+				"incoming_rate": self.sle.incoming_rate,
+			},
+		)
+
+	def create_batch(self):
+		from erpnext.stock.doctype.batch.batch import make_batch
+
+		return make_batch(
+			frappe._dict(
+				{
+					"item": self.item_code,
+					"reference_doctype": self.sle.voucher_type,
+					"reference_name": self.sle.voucher_no,
+				}
+			)
+		)
+
+	def process_batch_no(self):
+		if (
+			not self.sle.is_cancelled
+			and not self.sle.serial_and_batch_bundle
+			and self.sle.actual_qty > 0
+			and self.item_details.has_batch_no == 1
+			and self.item_details.create_new_batch
+			and self.item_details.batch_number_series
+		):
+			self.make_serial_no_bundle()
+
+
+class RepostSerialBatchBundle:
+	def __init__(self, **kwargs):
+		for key, value in kwargs.iteritems():
+			setattr(self, key, value)
+
+	def get_valuation_rate(self):
+		if self.sle.actual_qty > 0:
+			self.sle.incoming_rate = self.sle.valuation_rate
+
+		if self.sle.actual_qty < 0:
+			self.sle.outgoing_rate = self.sle.valuation_rate
+
+	def get_valuation_rate_for_serial_nos(self):
+		serial_nos = self.get_serial_nos()
+
+		subquery = f"""
+			SELECT
+				MAX(ledger.posting_date), name
+			FROM
+				ledger
+			WHERE
+				ledger.serial_no IN {tuple(serial_nos)}
+				AND ledger.is_outward = 0
+				AND ledger.warehouse = {frappe.db.escape(self.sle.warehouse)}
+				AND ledger.item_code = {frappe.db.escape(self.sle.item_code)}
+				AND (
+					ledger.posting_date < '{self.sle.posting_date}'
+					OR (
+						ledger.posting_date = '{self.sle.posting_date}'
+						AND ledger.posting_time <= '{self.sle.posting_time}'
+					)
+				)
+		"""
+
+		frappe.db.sql(
+			"""
+			SELECT
+				serial_no, incoming_rate
+			FROM
+				`tabSerial and Batch Ledger` AS ledger,
+				({subquery}) AS SubQuery
+			WHERE
+				ledger.name = SubQuery.name
+			GROUP BY
+				ledger.serial_no
+		"""
+		)
+
+	def get_serial_nos(self):
+		ledgers = frappe.get_all(
+			"Serial and Batch Ledger",
+			fields=["serial_no"],
+			filters={"parent": self.sle.serial_and_batch_bundle, "is_outward": 1},
+		)
+
+		return [d.serial_no for d in ledgers]
+
+
+class DeprecatedRepostSerialBatchBundle(RepostSerialBatchBundle):
+	def get_serialized_values(self, sle):
+		incoming_rate = flt(sle.incoming_rate)
+		actual_qty = flt(sle.actual_qty)
+		serial_nos = cstr(sle.serial_no).split("\n")
+
+		if incoming_rate < 0:
+			# wrong incoming rate
+			incoming_rate = self.wh_data.valuation_rate
+
+		stock_value_change = 0
+		if actual_qty > 0:
+			stock_value_change = actual_qty * incoming_rate
+		else:
+			# In case of delivery/stock issue, get average purchase rate
+			# of serial nos of current entry
+			if not sle.is_cancelled:
+				outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos)
+				stock_value_change = -1 * outgoing_value
+			else:
+				stock_value_change = actual_qty * sle.outgoing_rate
+
+		new_stock_qty = self.wh_data.qty_after_transaction + actual_qty
+
+		if new_stock_qty > 0:
+			new_stock_value = (
+				self.wh_data.qty_after_transaction * self.wh_data.valuation_rate
+			) + stock_value_change
+			if new_stock_value >= 0:
+				# calculate new valuation rate only if stock value is positive
+				# else it remains the same as that of previous entry
+				self.wh_data.valuation_rate = new_stock_value / new_stock_qty
+
+		if not self.wh_data.valuation_rate and sle.voucher_detail_no:
+			allow_zero_rate = self.check_if_allow_zero_valuation_rate(
+				sle.voucher_type, sle.voucher_detail_no
+			)
+			if not allow_zero_rate:
+				self.wh_data.valuation_rate = self.get_fallback_rate(sle)
+
+	def get_incoming_value_for_serial_nos(self, sle, serial_nos):
+		# get rate from serial nos within same company
+		all_serial_nos = frappe.get_all(
+			"Serial No", fields=["purchase_rate", "name", "company"], filters={"name": ("in", serial_nos)}
+		)
+
+		incoming_values = sum(flt(d.purchase_rate) for d in all_serial_nos if d.company == sle.company)
+
+		# Get rate for serial nos which has been transferred to other company
+		invalid_serial_nos = [d.name for d in all_serial_nos if d.company != sle.company]
+		for serial_no in invalid_serial_nos:
+			incoming_rate = frappe.db.sql(
+				"""
+				select incoming_rate
+				from `tabStock Ledger Entry`
+				where
+					company = %s
+					and actual_qty > 0
+					and is_cancelled = 0
+					and (serial_no = %s
+						or serial_no like %s
+						or serial_no like %s
+						or serial_no like %s
+					)
+				order by posting_date desc
+				limit 1
+			""",
+				(sle.company, serial_no, serial_no + "\n%", "%\n" + serial_no, "%\n" + serial_no + "\n%"),
+			)
+
+			incoming_values += flt(incoming_rate[0][0]) if incoming_rate else 0
+
+		return incoming_values
+
+	def update_batched_values(self, sle):
+		incoming_rate = flt(sle.incoming_rate)
+		actual_qty = flt(sle.actual_qty)
+
+		self.wh_data.qty_after_transaction = round_off_if_near_zero(
+			self.wh_data.qty_after_transaction + actual_qty
+		)
+
+		if actual_qty > 0:
+			stock_value_difference = incoming_rate * actual_qty
+		else:
+			outgoing_rate = get_batch_incoming_rate(
+				item_code=sle.item_code,
+				warehouse=sle.warehouse,
+				batch_no=sle.batch_no,
+				posting_date=sle.posting_date,
+				posting_time=sle.posting_time,
+				creation=sle.creation,
+			)
+			if outgoing_rate is None:
+				# This can *only* happen if qty available for the batch is zero.
+				# in such case fall back various other rates.
+				# future entries will correct the overall accounting as each
+				# batch individually uses moving average rates.
+				outgoing_rate = self.get_fallback_rate(sle)
+			stock_value_difference = outgoing_rate * actual_qty
+
+		self.wh_data.stock_value = round_off_if_near_zero(
+			self.wh_data.stock_value + stock_value_difference
+		)
+		if self.wh_data.qty_after_transaction:
+			self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
+
+
+def get_batch_incoming_rate(
+	item_code, warehouse, batch_no, posting_date, posting_time, creation=None
+):
+
+	sle = frappe.qb.DocType("Stock Ledger Entry")
+
+	timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
+		posting_date, posting_time
+	)
+	if creation:
+		timestamp_condition |= (
+			CombineDatetime(sle.posting_date, sle.posting_time)
+			== CombineDatetime(posting_date, posting_time)
+		) & (sle.creation < creation)
+
+	batch_details = (
+		frappe.qb.from_(sle)
+		.select(Sum(sle.stock_value_difference).as_("batch_value"), Sum(sle.actual_qty).as_("batch_qty"))
+		.where(
+			(sle.item_code == item_code)
+			& (sle.warehouse == warehouse)
+			& (sle.batch_no == batch_no)
+			& (sle.is_cancelled == 0)
+		)
+		.where(timestamp_condition)
+	).run(as_dict=True)
+
+	if batch_details and batch_details[0].batch_qty:
+		return batch_details[0].batch_value / batch_details[0].batch_qty