fix: travis for POS merge invoice and putaway rule
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index db64d06..d8cbcc1 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -387,7 +387,7 @@
 	]
 	for pos_invoice in pos_return_docs:
 		for item in pos_invoice.items:
-			if not item.serial_no:
+			if not item.serial_no and not item.serial_and_batch_bundle:
 				continue
 
 			return_against_is_added = any(
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
index 9e696f1..6af8a00 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -13,6 +13,9 @@
 from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
 	consolidate_pos_invoices,
 )
+from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
+	get_serial_nos_from_bundle,
+)
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 
 
@@ -410,13 +413,13 @@
 
 		try:
 			se = make_serialized_item()
-			serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
+			serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
 
 			init_user_and_profile()
 
 			pos_inv = create_pos_invoice(
 				item_code="_Test Serialized Item With Series",
-				serial_no=serial_no,
+				serial_no=[serial_no],
 				qty=1,
 				rate=100,
 				do_not_submit=1,
@@ -430,7 +433,7 @@
 
 			pos_inv2 = create_pos_invoice(
 				item_code="_Test Serialized Item With Series",
-				serial_no=serial_no,
+				serial_no=[serial_no],
 				qty=1,
 				rate=100,
 				do_not_submit=1,
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 9fa7a86..51e0d91 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2981,7 +2981,7 @@
 
 		# Sales Invoice with Payment Schedule
 		si_with_payment_schedule = create_sales_invoice(do_not_submit=True)
-		si_with_payment_schedule.extend(
+		si_with_payment_schedule.set(
 			"payment_schedule",
 			[
 				{
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 34e3b13..11cee28 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -663,19 +663,31 @@
 	return filters
 
 
-def get_returned_serial_nos(child_doc, parent_doc, serial_no_field=None):
+def get_returned_serial_nos(
+	child_doc, parent_doc, serial_no_field=None, ignore_voucher_detail_no=None
+):
+	from erpnext.stock.doctype.serial_no.serial_no import (
+		get_serial_nos as get_serial_nos_from_serial_no,
+	)
 	from erpnext.stock.serial_batch_bundle import get_serial_nos
 
 	if not serial_no_field:
 		serial_no_field = "serial_and_batch_bundle"
 
+	old_field = "serial_no"
+	if serial_no_field == "rejected_serial_and_batch_bundle":
+		old_field = "rejected_serial_no"
+
 	return_ref_field = frappe.scrub(child_doc.doctype)
 	if child_doc.doctype == "Delivery Note Item":
 		return_ref_field = "dn_detail"
 
 	serial_nos = []
 
-	fields = [f"`{'tab' + child_doc.doctype}`.`{serial_no_field}`"]
+	fields = [
+		f"`{'tab' + child_doc.doctype}`.`{serial_no_field}`",
+		f"`{'tab' + child_doc.doctype}`.`{old_field}`",
+	]
 
 	filters = [
 		[parent_doc.doctype, "return_against", "=", parent_doc.name],
@@ -684,9 +696,15 @@
 		[parent_doc.doctype, "docstatus", "=", 1],
 	]
 
+	# Required for POS Invoice
+	if ignore_voucher_detail_no:
+		filters.append([child_doc.doctype, "name", "!=", ignore_voucher_detail_no])
+
 	ids = []
 	for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
 		ids.append(row.get("serial_and_batch_bundle"))
+		if row.get(old_field):
+			serial_nos.extend(get_serial_nos_from_serial_no(row.get(old_field)))
 
 	serial_nos.extend(get_serial_nos(ids))
 
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index ff2d705..15a72a8 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -931,7 +931,7 @@
 				"is_stock_item": 1,
 				"has_batch_no": 1,
 				"create_new_batch": 1,
-				"batch_number_series": "TESTBATCH.#####",
+				"batch_number_series": "TESTBATCHIUU.#####",
 			},
 		)
 		make_product_bundle(parent=batched_bundle.name, items=[batched_item.name])
@@ -942,7 +942,7 @@
 		dn = create_delivery_note(item_code=batched_bundle.name, qty=1)
 		dn.load_from_db()
 
-		batch_no = get_batch_from_bundle(dn.items[0].serial_and_batch_bundle)
+		batch_no = get_batch_from_bundle(dn.packed_items[0].serial_and_batch_bundle)
 		self.assertTrue(batch_no)
 
 	def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
index 623fbde..0a04210 100644
--- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
@@ -11,7 +11,6 @@
 from frappe.model.document import Document
 from frappe.utils import cint, cstr, floor, flt, nowdate
 
-from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 from erpnext.stock.utils import get_stock_balance
 
 
@@ -99,7 +98,6 @@
 			item = frappe._dict(item)
 
 		source_warehouse = item.get("s_warehouse")
-		serial_nos = get_serial_nos(item.get("serial_no"))
 		item.conversion_factor = flt(item.conversion_factor) or 1.0
 		pending_qty, item_code = flt(item.qty), item.item_code
 		pending_stock_qty = flt(item.transfer_qty) if doctype == "Stock Entry" else flt(item.stock_qty)
@@ -145,9 +143,7 @@
 				if not qty_to_allocate:
 					break
 
-				updated_table = add_row(
-					item, qty_to_allocate, rule.warehouse, updated_table, rule.name, serial_nos=serial_nos
-				)
+				updated_table = add_row(item, qty_to_allocate, rule.warehouse, updated_table, rule.name)
 
 				pending_stock_qty -= stock_qty_to_allocate
 				pending_qty -= qty_to_allocate
@@ -245,7 +241,7 @@
 	return False, vacant_rules
 
 
-def add_row(item, to_allocate, warehouse, updated_table, rule=None, serial_nos=None):
+def add_row(item, to_allocate, warehouse, updated_table, rule=None):
 	new_updated_table_row = copy.deepcopy(item)
 	new_updated_table_row.idx = 1 if not updated_table else cint(updated_table[-1].idx) + 1
 	new_updated_table_row.name = None
@@ -264,8 +260,8 @@
 
 	if rule:
 		new_updated_table_row.putaway_rule = rule
-	if serial_nos:
-		new_updated_table_row.serial_no = get_serial_nos_to_allocate(serial_nos, to_allocate)
+
+	new_updated_table_row.serial_and_batch_bundle = ""
 
 	updated_table.append(new_updated_table_row)
 	return updated_table
@@ -297,12 +293,3 @@
 	)
 
 	frappe.msgprint(msg, title=_("Insufficient Capacity"), is_minimizable=True, wide=True)
-
-
-def get_serial_nos_to_allocate(serial_nos, to_allocate):
-	if serial_nos:
-		allocated_serial_nos = serial_nos[0 : cint(to_allocate)]
-		serial_nos[:] = serial_nos[cint(to_allocate) :]  # pop out allocated serial nos and modify list
-		return "\n".join(allocated_serial_nos) if allocated_serial_nos else ""
-	else:
-		return ""
diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
index ab0ca10..f5bad51 100644
--- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
@@ -7,6 +7,11 @@
 from erpnext.stock.doctype.batch.test_batch import make_new_batch
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
+	get_batch_from_bundle,
+	get_serial_nos_from_bundle,
+	make_serial_batch_bundle,
+)
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
 from erpnext.stock.get_item_details import get_conversion_factor
@@ -382,42 +387,49 @@
 		make_new_batch(batch_id="BOTTL-BATCH-1", item_code="Water Bottle")
 
 		pr = make_purchase_receipt(item_code="Water Bottle", qty=5, do_not_submit=1)
-		pr.items[0].batch_no = "BOTTL-BATCH-1"
 		pr.save()
 		pr.submit()
+		pr.load_from_db()
 
-		serial_nos = frappe.get_list(
-			"Serial No", filters={"purchase_document_no": pr.name, "status": "Active"}
-		)
-		serial_nos = [d.name for d in serial_nos]
+		batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
+		serial_nos = get_serial_nos_from_bundle(pr.items[0].serial_and_batch_bundle)
 
 		stock_entry = make_stock_entry(
 			item_code="Water Bottle",
 			source="_Test Warehouse - _TC",
 			qty=5,
+			serial_no=serial_nos,
 			target="Finished Goods - _TC",
 			purpose="Material Transfer",
 			apply_putaway_rule=1,
 			do_not_save=1,
 		)
-		stock_entry.items[0].batch_no = "BOTTL-BATCH-1"
-		stock_entry.items[0].serial_no = "\n".join(serial_nos)
 		stock_entry.save()
+		stock_entry.load_from_db()
 
 		self.assertEqual(stock_entry.items[0].t_warehouse, self.warehouse_1)
 		self.assertEqual(stock_entry.items[0].qty, 3)
 		self.assertEqual(stock_entry.items[0].putaway_rule, rule_1.name)
-		self.assertEqual(stock_entry.items[0].serial_no, "\n".join(serial_nos[:3]))
-		self.assertEqual(stock_entry.items[0].batch_no, "BOTTL-BATCH-1")
+		self.assertEqual(
+			get_serial_nos_from_bundle(stock_entry.items[0].serial_and_batch_bundle), serial_nos[0:3]
+		)
+		self.assertEqual(get_batch_from_bundle(stock_entry.items[0].serial_and_batch_bundle), batch_no)
 
 		self.assertEqual(stock_entry.items[1].t_warehouse, self.warehouse_2)
 		self.assertEqual(stock_entry.items[1].qty, 2)
 		self.assertEqual(stock_entry.items[1].putaway_rule, rule_2.name)
-		self.assertEqual(stock_entry.items[1].serial_no, "\n".join(serial_nos[3:]))
-		self.assertEqual(stock_entry.items[1].batch_no, "BOTTL-BATCH-1")
+		self.assertEqual(
+			get_serial_nos_from_bundle(stock_entry.items[1].serial_and_batch_bundle), serial_nos[3:5]
+		)
+		self.assertEqual(get_batch_from_bundle(stock_entry.items[1].serial_and_batch_bundle), batch_no)
 
 		self.assertUnchangedItemsOnResave(stock_entry)
 
+		for row in stock_entry.items:
+			if row.serial_and_batch_bundle:
+				frappe.delete_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
+
+		stock_entry.load_from_db()
 		stock_entry.delete()
 		pr.cancel()
 		rule_1.delete()
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 cfb03f0..9f26b40 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
@@ -6,7 +6,7 @@
 from typing import Dict, List
 
 import frappe
-from frappe import _, bold
+from frappe import _, _dict, bold
 from frappe.model.document import Document
 from frappe.query_builder.functions import CombineDatetime, Sum
 from frappe.utils import add_days, cint, flt, get_link_to_form, nowtime, today
@@ -82,16 +82,20 @@
 			return
 
 		serial_nos = [d.serial_no for d in self.entries if d.serial_no]
-		available_serial_nos = get_available_serial_nos(
-			frappe._dict(
-				{
-					"item_code": self.item_code,
-					"posting_date": self.posting_date,
-					"posting_time": self.posting_time,
-				}
-			)
+		kwargs = frappe._dict(
+			{
+				"item_code": self.item_code,
+				"posting_date": self.posting_date,
+				"posting_time": self.posting_time,
+				"serial_nos": serial_nos,
+			}
 		)
 
+		if self.returned_against and self.docstatus == 1:
+			kwargs["ignore_voucher_detail_no"] = self.voucher_detail_no
+
+		available_serial_nos = get_available_serial_nos(kwargs)
+
 		for data in available_serial_nos:
 			if data.serial_no in serial_nos:
 				self.throw_error_message(
@@ -776,6 +780,10 @@
 
 	ignore_serial_nos = get_reserved_serial_nos_for_pos(kwargs)
 
+	# To ignore serial nos in the same record for the draft state
+	if kwargs.get("ignore_serial_nos"):
+		ignore_serial_nos.extend(kwargs.get("ignore_serial_nos"))
+
 	if kwargs.get("posting_date"):
 		if kwargs.get("posting_time") is None:
 			kwargs.posting_time = nowtime()
@@ -801,7 +809,7 @@
 
 	for d in data:
 		if d.serial_and_batch_bundle:
-			sns = get_serial_nos_from_bundle(d.serial_and_batch_bundle)
+			sns = get_serial_nos_from_bundle(d.serial_and_batch_bundle, kwargs.get("serial_nos", []))
 			if d.actual_qty > 0:
 				serial_nos.update(sns)
 			else:
@@ -823,12 +831,19 @@
 
 
 def get_reserved_serial_nos_for_pos(kwargs):
+	from erpnext.controllers.sales_and_purchase_return import get_returned_serial_nos
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
 	ignore_serial_nos = []
 	pos_invoices = frappe.get_all(
 		"POS Invoice",
-		fields=["`tabPOS Invoice Item`.serial_no", "`tabPOS Invoice Item`.serial_and_batch_bundle"],
+		fields=[
+			"`tabPOS Invoice Item`.serial_no",
+			"`tabPOS Invoice`.is_return",
+			"`tabPOS Invoice Item`.name as child_docname",
+			"`tabPOS Invoice`.name as parent_docname",
+			"`tabPOS Invoice Item`.serial_and_batch_bundle",
+		],
 		filters=[
 			["POS Invoice", "consolidated_invoice", "is", "not set"],
 			["POS Invoice", "docstatus", "=", 1],
@@ -850,11 +865,35 @@
 		ignore_serial_nos.append(d.serial_no)
 
 	# Will be deprecated in v16
+	returned_serial_nos = []
 	for pos_invoice in pos_invoices:
 		if pos_invoice.serial_no:
 			ignore_serial_nos.extend(get_serial_nos(pos_invoice.serial_no))
 
-	return ignore_serial_nos
+		if pos_invoice.is_return:
+			continue
+
+		child_doc = _dict(
+			{
+				"doctype": "POS Invoice Item",
+				"name": pos_invoice.child_docname,
+			}
+		)
+
+		parent_doc = _dict(
+			{
+				"doctype": "POS Invoice",
+				"name": pos_invoice.parent_docname,
+			}
+		)
+
+		returned_serial_nos.extend(
+			get_returned_serial_nos(
+				child_doc, parent_doc, ignore_voucher_detail_no=kwargs.get("ignore_voucher_detail_no")
+			)
+		)
+
+	return list(set(ignore_serial_nos) - set(returned_serial_nos))
 
 
 def get_auto_batch_nos(kwargs):
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index 68623fb..4a0abb6 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -11,6 +11,11 @@
 from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
+	get_batch_from_bundle,
+	get_serial_nos_from_bundle,
+	make_serial_batch_bundle,
+)
 from erpnext.stock.doctype.serial_no.serial_no import *
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -209,23 +214,6 @@
 		self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
 		self.assertEqual(sn_doc.purchase_document_no, se.name)
 
-	def test_auto_creation_of_serial_no(self):
-		"""
-		Test if auto created Serial No excludes existing serial numbers
-		"""
-		item_code = make_item(
-			"_Test Auto Serial Item ", {"has_serial_no": 1, "serial_no_series": "XYZ.###"}
-		).item_code
-
-		# Reserve XYZ005
-		pr_1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no="XYZ005")
-		# XYZ005 is already used and will throw an error if used again
-		pr_2 = make_purchase_receipt(item_code=item_code, qty=10)
-
-		self.assertEqual(get_serial_nos(pr_1.get("items")[0].serial_no)[0], "XYZ005")
-		for serial_no in get_serial_nos(pr_2.get("items")[0].serial_no):
-			self.assertNotEqual(serial_no, "XYZ005")
-
 	def test_serial_no_sanitation(self):
 		"Test if Serial No input is sanitised before entering the DB."
 		item_code = "_Test Serialized Item"
@@ -288,12 +276,12 @@
 		in1.reload()
 		in2.reload()
 
-		batch1 = in1.items[0].batch_no
-		batch2 = in2.items[0].batch_no
+		batch1 = get_batch_from_bundle(in1.items[0].serial_and_batch_bundle)
+		batch2 = get_batch_from_bundle(in2.items[0].serial_and_batch_bundle)
 
 		batch_wise_serials = {
-			batch1: get_serial_nos(in1.items[0].serial_no),
-			batch2: get_serial_nos(in2.items[0].serial_no),
+			batch1: get_serial_nos_from_bundle(in1.items[0].serial_and_batch_bundle),
+			batch2: get_serial_nos_from_bundle(in2.items[0].serial_and_batch_bundle),
 		}
 
 		# Test FIFO
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index e686e58..2f49822 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -142,7 +142,6 @@
 		self.validate_job_card_item()
 		self.set_purpose_for_stock_entry()
 		self.clean_serial_nos()
-		self.validate_duplicate_serial_no()
 
 		if not self.from_bom:
 			self.fg_completed_qty = 0.0
@@ -878,52 +877,63 @@
 		if self.stock_entry_type and not self.purpose:
 			self.purpose = frappe.get_cached_value("Stock Entry Type", self.stock_entry_type, "purpose")
 
-	def validate_duplicate_serial_no(self):
-		warehouse_wise_serial_nos = {}
-
-		# In case of repack the source and target serial nos could be same
-		for warehouse in ["s_warehouse", "t_warehouse"]:
-			serial_nos = []
-			for row in self.items:
-				if not (row.serial_no and row.get(warehouse)):
-					continue
-
-				for sn in get_serial_nos(row.serial_no):
-					if sn in serial_nos:
-						frappe.throw(
-							_("The serial no {0} has added multiple times in the stock entry {1}").format(
-								frappe.bold(sn), self.name
-							)
-						)
-
-					serial_nos.append(sn)
-
 	def make_serial_and_batch_bundle_for_outward(self):
+		if self.docstatus == 1:
+			return
+
 		serial_or_batch_items = get_serial_or_batch_items(self.items)
 		if not serial_or_batch_items:
 			return
 
+		already_picked_serial_nos = []
+
 		for row in self.items:
 			if not row.s_warehouse:
 				continue
 
-			if row.serial_and_batch_bundle or row.item_code not in serial_or_batch_items:
+			if row.item_code not in serial_or_batch_items:
 				continue
 
-			bundle_doc = SerialBatchCreation(
-				{
-					"item_code": row.item_code,
-					"warehouse": row.s_warehouse,
-					"posting_date": self.posting_date,
-					"posting_time": self.posting_time,
-					"voucher_type": self.doctype,
-					"voucher_detail_no": row.name,
-					"qty": row.qty * -1,
-					"type_of_transaction": "Outward",
-					"company": self.company,
-					"do_not_submit": True,
-				}
-			).make_serial_and_batch_bundle()
+			bundle_doc = None
+			if row.serial_and_batch_bundle and abs(row.qty) != abs(
+				frappe.get_cached_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "total_qty")
+			):
+				bundle_doc = SerialBatchCreation(
+					{
+						"item_code": row.item_code,
+						"warehouse": row.s_warehouse,
+						"serial_and_batch_bundle": row.serial_and_batch_bundle,
+						"type_of_transaction": "Outward",
+						"ignore_serial_nos": already_picked_serial_nos,
+						"qty": row.qty * -1,
+					}
+				).update_serial_and_batch_entries()
+			elif not row.serial_and_batch_bundle:
+				bundle_doc = SerialBatchCreation(
+					{
+						"item_code": row.item_code,
+						"warehouse": row.s_warehouse,
+						"posting_date": self.posting_date,
+						"posting_time": self.posting_time,
+						"voucher_type": self.doctype,
+						"voucher_detail_no": row.name,
+						"qty": row.qty * -1,
+						"ignore_serial_nos": already_picked_serial_nos,
+						"type_of_transaction": "Outward",
+						"company": self.company,
+						"do_not_submit": True,
+					}
+				).make_serial_and_batch_bundle()
+
+			if not bundle_doc:
+				continue
+
+			if self.docstatus == 0:
+				for entry in bundle_doc.entries:
+					if not entry.serial_no:
+						continue
+
+					already_picked_serial_nos.append(entry.serial_no)
 
 			row.serial_and_batch_bundle = bundle_doc.name
 
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 06fe0f1..33dd960 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -255,11 +255,14 @@
 			frappe.db.set_value("Batch", batch_no, "batch_qty", batches_qty.get(batch_no, 0))
 
 
-def get_serial_nos(serial_and_batch_bundle):
+def get_serial_nos(serial_and_batch_bundle, serial_nos=None):
 	filters = {"parent": serial_and_batch_bundle}
 	if isinstance(serial_and_batch_bundle, list):
 		filters = {"parent": ("in", serial_and_batch_bundle)}
 
+	if serial_nos:
+		filters["serial_no"] = ("in", serial_nos)
+
 	entries = frappe.get_all("Serial and Batch Entry", fields=["serial_no"], filters=filters)
 
 	return [d.serial_no for d in entries]
@@ -694,6 +697,18 @@
 
 		return doc
 
+	def update_serial_and_batch_entries(self):
+		doc = frappe.get_doc("Serial and Batch Bundle", self.serial_and_batch_bundle)
+		doc.type_of_transaction = self.type_of_transaction
+		doc.set("entries", [])
+		self.set_auto_serial_batch_entries_for_outward()
+		self.set_serial_batch_entries(doc)
+		if not doc.get("entries"):
+			return frappe._dict({})
+
+		doc.save()
+		return doc
+
 	def set_auto_serial_batch_entries_for_outward(self):
 		from erpnext.stock.doctype.batch.batch import get_available_batches
 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos_for_outward
@@ -707,6 +722,9 @@
 			}
 		)
 
+		if self.get("ignore_serial_nos"):
+			kwargs["ignore_serial_nos"] = self.ignore_serial_nos
+
 		if self.has_serial_no and not self.get("serial_nos"):
 			self.serial_nos = get_serial_nos_for_outward(kwargs)
 		elif not self.has_serial_no and self.has_batch_no and not self.get("batches"):