Merge pull request #39719 from niyazrazak/patch-18

feat: project field in installation note
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index ccdfc8c..1e5125e 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -31,6 +31,9 @@
   test:
     runs-on: ubuntu-latest
     timeout-minutes: 60
+    env:
+      NODE_ENV: "production"
+      WITH_COVERAGE: ${{ github.event_name != 'pull_request' }}
 
     strategy:
       fail-fast: false
@@ -117,11 +120,11 @@
           FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
 
       - name: Run Tests
-        run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --with-coverage --total-builds 4 --build-number ${{ matrix.container }}'
+        run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 4 --build-number ${{ matrix.container }}'
         env:
           TYPE: server
-          CI_BUILD_ID: ${{ github.run_id }}
-          ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
+          CAPTURE_COVERAGE: ${{ github.event_name != 'pull_request' }}
+
 
       - name: Show bench output
         if: ${{ always() }}
@@ -129,6 +132,7 @@
 
       - name: Upload coverage data
         uses: actions/upload-artifact@v3
+        if: github.event_name != 'pull_request'
         with:
           name: coverage-${{ matrix.container }}
           path: /home/runner/frappe-bench/sites/coverage.xml
@@ -137,6 +141,7 @@
     name: Coverage Wrap Up
     needs: test
     runs-on: ubuntu-latest
+    if: ${{ github.event_name != 'pull_request' }}
     steps:
       - name: Clone
         uses: actions/checkout@v2
@@ -148,5 +153,6 @@
         uses: codecov/codecov-action@v2
         with:
           name: MariaDB
+          token: ${{ secrets.CODECOV_TOKEN }}
           fail_ci_if_error: true
           verbose: true
diff --git a/README.md b/README.md
index 710187a..4f65ceb 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,7 @@
         <p>ERP made simple</p>
     </p>
 
-[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
-[![UI](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml/badge.svg?branch=develop&event=schedule)](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml)
+[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml/badge.svg?event=schedule)](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml)
 [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
 [![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext)
 [![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker)
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/br_planilha_de_contas.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/br_planilha_de_contas.json
index a1dbddc..45be1e3 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/br_planilha_de_contas.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/br_planilha_de_contas.json
@@ -56,7 +56,9 @@
                     "Constru\u00e7\u00f5es em Andamento de Im\u00f3veis Destinados \u00e0 Venda": {}, 
                     "Estoques Destinados \u00e0 Doa\u00e7\u00e3o": {}, 
                     "Im\u00f3veis Destinados \u00e0 Venda": {}, 
-                    "Insumos (materiais diretos)": {}, 
+                    "Insumos (materiais diretos)": {
+                        "account_type": "Stock"
+                    }, 
                     "Insumos Agropecu\u00e1rios": {}, 
                     "Mercadorias para Revenda": {}, 
                     "Outras 11": {}, 
@@ -146,6 +148,65 @@
             "root_type": "Asset"
         }, 
         "CUSTOS DE PRODU\u00c7\u00c3O": {
+            "CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
+                "CUSTO DOS PRODUTOS VENDIDOS": {
+                    "CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
+                        "Custos dos Produtos Vendidos em Geral": {
+                            "account_type": "Cost of Goods Sold"
+                        }, 
+                        "Outros Custos 4": {}, 
+                        "account_type": "Cost of Goods Sold"
+                    }, 
+                    "CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
+                        "Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {}, 
+                        "Custos dos Produtos para Assist\u00eancia Social - Vendidos": {}, 
+                        "Outras": {}
+                    }, 
+                    "CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
+                        "Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {}, 
+                        "Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {}, 
+                        "Outros Custos 6": {}
+                    }, 
+                    "CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
+                        "Custos dos Produtos para Sa\u00fade - Gratuidades": {}, 
+                        "Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {}, 
+                        "Outros Custos 5": {}
+                    }, 
+                    "account_type": "Cost of Goods Sold"
+                }, 
+                "CUSTO DOS SERVI\u00c7OS PRESTADOS": {
+                    "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
+                        "Custo dos Servi\u00e7os Prestados em Geral": {}, 
+                        "Outros Custos": {}
+                    }, 
+                    "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
+                        "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Gratuidade 1": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {}, 
+                        "Outros Custos 2": {}
+                    }, 
+                    "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
+                        "Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Gratuidade": {}, 
+                        "Custo dos Servi\u00e7os Prestados ao PROUNI": {}, 
+                        "Outros Custos 1": {}
+                    }, 
+                    "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
+                        "Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Gratuidade 2": {}, 
+                        "Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {}, 
+                        "Outros Custos 3": {}
+                    }
+                }
+            }, 
             "CUSTO DOS BENS E SERVI\u00c7OS PRODUZIDOS": {
                 "CUSTO DOS PRODUTOS DE FABRICA\u00c7\u00c3O PR\u00d3PRIA PRODUZIDOS": {
                     "Alimenta\u00e7\u00e3o do Trabalhador": {}, 
@@ -621,7 +682,9 @@
                             "Receita das Unidades Imobili\u00e1rias Vendidas": {}, 
                             "Receita de Exporta\u00e7\u00e3o Direta de Mercadorias e Produtos": {}, 
                             "Receita de Exporta\u00e7\u00e3o de Servi\u00e7os": {}, 
-                            "Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": {}, 
+                            "Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": {
+                                "account_type": "Income Account"
+                            }, 
                             "Receita de Vendas de Mercadorias e Produtos a Comercial Exportadora com Fim Espec\u00edfico de Exporta\u00e7\u00e3o": {}
                         }
                     }
@@ -645,65 +708,6 @@
                 }
             }, 
             "RESULTADO OPERACIONAL": {
-                "CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
-                    "CUSTO DOS PRODUTOS VENDIDOS": {
-                        "CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
-                            "Custos dos Produtos Vendidos em Geral": {
-                                "account_type": "Cost of Goods Sold"
-                            }, 
-                            "Outros Custos 4": {}, 
-                            "account_type": "Cost of Goods Sold"
-                        }, 
-                        "CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
-                            "Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {}, 
-                            "Custos dos Produtos para Assist\u00eancia Social - Vendidos": {}, 
-                            "Outras": {}
-                        }, 
-                        "CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
-                            "Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {}, 
-                            "Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {}, 
-                            "Outros Custos 6": {}
-                        }, 
-                        "CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
-                            "Custos dos Produtos para Sa\u00fade - Gratuidades": {}, 
-                            "Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {}, 
-                            "Outros Custos 5": {}
-                        }, 
-                        "account_type": "Cost of Goods Sold"
-                    }, 
-                    "CUSTO DOS SERVI\u00c7OS PRESTADOS": {
-                        "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
-                            "Custo dos Servi\u00e7os Prestados em Geral": {}, 
-                            "Outros Custos": {}
-                        }, 
-                        "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
-                            "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Gratuidade 1": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {}, 
-                            "Outros Custos 2": {}
-                        }, 
-                        "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
-                            "Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Gratuidade": {}, 
-                            "Custo dos Servi\u00e7os Prestados ao PROUNI": {}, 
-                            "Outros Custos 1": {}
-                        }, 
-                        "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
-                            "Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Gratuidade 2": {}, 
-                            "Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {}, 
-                            "Outros Custos 3": {}
-                        }
-                    }
-                }, 
                 "DESPESAS OPERACIONAIS": {
                     "DESPESAS OPERACIONAIS 1": {
                         "DESPESAS OPERACIONAIS 2": {
diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py
index ace4bb1..df4bd56 100644
--- a/erpnext/accounts/doctype/bank_account/bank_account.py
+++ b/erpnext/accounts/doctype/bank_account/bank_account.py
@@ -9,6 +9,7 @@
 	load_address_and_contact,
 )
 from frappe.model.document import Document
+from frappe.utils import comma_and, get_link_to_form
 
 
 class BankAccount(Document):
@@ -52,6 +53,17 @@
 	def validate(self):
 		self.validate_company()
 		self.validate_iban()
+		self.validate_account()
+
+	def validate_account(self):
+		if self.account:
+			if accounts := frappe.db.get_all("Bank Account", filters={"account": self.account}, as_list=1):
+				frappe.throw(
+					_("'{0}' account is already used by {1}. Use another account.").format(
+						frappe.bold(self.account),
+						frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
+					)
+				)
 
 	def validate_company(self):
 		if self.is_company_account and not self.company:
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index fef3b56..5e17881 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -94,10 +94,13 @@
 			pe.append(reference)
 
 	def update_allocated_amount(self):
-		self.allocated_amount = (
+		allocated_amount = (
 			sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
 		)
-		self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.allocated_amount
+		unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - allocated_amount
+
+		self.allocated_amount = flt(allocated_amount, self.precision("allocated_amount"))
+		self.unallocated_amount = flt(unallocated_amount, self.precision("unallocated_amount"))
 
 	def before_submit(self):
 		self.allocate_payment_entries()
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index 7bb3f41..1fe3608 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -32,8 +32,16 @@
 			frappe.db.delete(dt)
 		clear_loan_transactions()
 		make_pos_profile()
-		add_transactions()
-		add_vouchers()
+
+		# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
+		uniq_identifier = frappe.generate_hash(length=10)
+		gl_account = create_gl_account("_Test Bank " + uniq_identifier)
+		bank_account = create_bank_account(
+			gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier
+		)
+
+		add_transactions(bank_account=bank_account)
+		add_vouchers(gl_account=gl_account)
 
 	# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
 	def test_linked_payments(self):
@@ -219,7 +227,9 @@
 	frappe.db.delete("Loan Repayment")
 
 
-def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
+def create_bank_account(
+	bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
+):
 	try:
 		frappe.get_doc(
 			{
@@ -231,21 +241,35 @@
 		pass
 
 	try:
-		frappe.get_doc(
+		bank_account = frappe.get_doc(
 			{
 				"doctype": "Bank Account",
-				"account_name": "Checking Account",
+				"account_name": bank_account_name,
 				"bank": bank_name,
-				"account": account_name,
+				"account": gl_account,
 			}
 		).insert(ignore_if_duplicate=True)
 	except frappe.DuplicateEntryError:
 		pass
 
+	return bank_account.name
 
-def add_transactions():
-	create_bank_account()
 
+def create_gl_account(gl_account_name="_Test Bank - _TC"):
+	gl_account = frappe.get_doc(
+		{
+			"doctype": "Account",
+			"company": "_Test Company",
+			"parent_account": "Current Assets - _TC",
+			"account_type": "Bank",
+			"is_group": 0,
+			"account_name": gl_account_name,
+		}
+	).insert()
+	return gl_account.name
+
+
+def add_transactions(bank_account="_Test Bank - _TC"):
 	doc = frappe.get_doc(
 		{
 			"doctype": "Bank Transaction",
@@ -253,7 +277,7 @@
 			"date": "2018-10-23",
 			"deposit": 1200,
 			"currency": "INR",
-			"bank_account": "Checking Account - Citi Bank",
+			"bank_account": bank_account,
 		}
 	).insert()
 	doc.submit()
@@ -265,7 +289,7 @@
 			"date": "2018-10-23",
 			"deposit": 1700,
 			"currency": "INR",
-			"bank_account": "Checking Account - Citi Bank",
+			"bank_account": bank_account,
 		}
 	).insert()
 	doc.submit()
@@ -277,7 +301,7 @@
 			"date": "2018-10-26",
 			"withdrawal": 690,
 			"currency": "INR",
-			"bank_account": "Checking Account - Citi Bank",
+			"bank_account": bank_account,
 		}
 	).insert()
 	doc.submit()
@@ -289,7 +313,7 @@
 			"date": "2018-10-27",
 			"deposit": 3900,
 			"currency": "INR",
-			"bank_account": "Checking Account - Citi Bank",
+			"bank_account": bank_account,
 		}
 	).insert()
 	doc.submit()
@@ -301,13 +325,13 @@
 			"date": "2018-10-27",
 			"withdrawal": 109080,
 			"currency": "INR",
-			"bank_account": "Checking Account - Citi Bank",
+			"bank_account": bank_account,
 		}
 	).insert()
 	doc.submit()
 
 
-def add_vouchers():
+def add_vouchers(gl_account="_Test Bank - _TC"):
 	try:
 		frappe.get_doc(
 			{
@@ -323,7 +347,7 @@
 
 	pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690)
 
-	pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
+	pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
 	pe.reference_no = "Conrad Oct 18"
 	pe.reference_date = "2018-10-24"
 	pe.insert()
@@ -342,14 +366,14 @@
 		pass
 
 	pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200)
-	pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
+	pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
 	pe.reference_no = "Herr G Oct 18"
 	pe.reference_date = "2018-10-24"
 	pe.insert()
 	pe.submit()
 
 	pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700)
-	pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
+	pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
 	pe.reference_no = "Herr G Nov 18"
 	pe.reference_date = "2018-11-01"
 	pe.insert()
@@ -380,10 +404,10 @@
 		pass
 
 	pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1)
-	pi.cash_bank_account = "_Test Bank - _TC"
+	pi.cash_bank_account = gl_account
 	pi.insert()
 	pi.submit()
-	pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
+	pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
 	pe.reference_no = "Poore Simon's Oct 18"
 	pe.reference_date = "2018-10-28"
 	pe.paid_amount = 690
@@ -392,7 +416,7 @@
 	pe.submit()
 
 	si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900)
-	pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
+	pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account)
 	pe.reference_no = "Poore Simon's Oct 18"
 	pe.reference_date = "2018-10-28"
 	pe.insert()
@@ -415,16 +439,12 @@
 	if not frappe.db.get_value(
 		"Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
 	):
-		mode_of_payment.append(
-			"accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"}
-		)
+		mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
 		mode_of_payment.save()
 
 	si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
 	si.is_pos = 1
-	si.append(
-		"payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080}
-	)
+	si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080})
 	si.insert()
 	si.submit()
 
diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
index c61c332..e3897bf 100644
--- a/erpnext/accounts/doctype/dunning/dunning.py
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -85,7 +85,14 @@
 				frappe.throw(
 					_(
 						"The currency of invoice {} ({}) is different from the currency of this dunning ({})."
-					).format(row.sales_invoice, invoice_currency, self.currency)
+					).format(
+						frappe.get_desk_link(
+							"Sales Invoice",
+							row.sales_invoice,
+						),
+						invoice_currency,
+						self.currency,
+					)
 				)
 
 	def validate_overdue_payments(self):
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 777a5bb..def2838 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -13,16 +13,9 @@
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_checks_for_pl_and_bs_accounts,
 )
-from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
-	get_dimension_filter_map,
-)
 from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency
 from erpnext.accounts.utils import get_account_currency, get_fiscal_year
-from erpnext.exceptions import (
-	InvalidAccountCurrency,
-	InvalidAccountDimensionError,
-	MandatoryAccountDimensionError,
-)
+from erpnext.exceptions import InvalidAccountCurrency
 
 exclude_from_linked_with = True
 
@@ -98,7 +91,6 @@
 		if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
 			self.validate_account_details(adv_adj)
 			self.validate_dimensions_for_pl_and_bs()
-			self.validate_allowed_dimensions()
 			validate_balance_type(self.account, adv_adj)
 			validate_frozen_account(self.account, adv_adj)
 
@@ -208,42 +200,6 @@
 						)
 					)
 
-	def validate_allowed_dimensions(self):
-		dimension_filter_map = get_dimension_filter_map()
-		for key, value in dimension_filter_map.items():
-			dimension = key[0]
-			account = key[1]
-
-			if self.account == account:
-				if value["is_mandatory"] and not self.get(dimension):
-					frappe.throw(
-						_("{0} is mandatory for account {1}").format(
-							frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
-						),
-						MandatoryAccountDimensionError,
-					)
-
-				if value["allow_or_restrict"] == "Allow":
-					if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
-						frappe.throw(
-							_("Invalid value {0} for {1} against account {2}").format(
-								frappe.bold(self.get(dimension)),
-								frappe.bold(frappe.unscrub(dimension)),
-								frappe.bold(self.account),
-							),
-							InvalidAccountDimensionError,
-						)
-				else:
-					if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
-						frappe.throw(
-							_("Invalid value {0} for {1} against account {2}").format(
-								frappe.bold(self.get(dimension)),
-								frappe.bold(frappe.unscrub(dimension)),
-								frappe.bold(self.account),
-							),
-							InvalidAccountDimensionError,
-						)
-
 	def check_pl_account(self):
 		if (
 			self.is_opening == "Yes"
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 7bf5324..18cf4ed 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -1169,7 +1169,9 @@
 
 
 @frappe.whitelist()
-def get_default_bank_cash_account(company, account_type=None, mode_of_payment=None, account=None):
+def get_default_bank_cash_account(
+	company, account_type=None, mode_of_payment=None, account=None, ignore_permissions=False
+):
 	from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
 
 	if mode_of_payment:
@@ -1207,7 +1209,7 @@
 		return frappe._dict(
 			{
 				"account": account,
-				"balance": get_balance_on(account),
+				"balance": get_balance_on(account, ignore_account_permission=ignore_permissions),
 				"account_currency": account_details.account_currency,
 				"account_type": account_details.account_type,
 			}
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index f23d2c9..c55c820 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -2220,6 +2220,7 @@
 	party_type=None,
 	payment_type=None,
 	reference_date=None,
+	ignore_permissions=False,
 ):
 	doc = frappe.get_doc(dt, dn)
 	over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
@@ -2242,14 +2243,14 @@
 	)
 
 	# bank or cash
-	bank = get_bank_cash_account(doc, bank_account)
+	bank = get_bank_cash_account(doc, bank_account, ignore_permissions=ignore_permissions)
 
 	# if default bank or cash account is not set in company master and party has default company bank account, fetch it
 	if party_type in ["Customer", "Supplier"] and not bank:
 		party_bank_account = get_party_bank_account(party_type, doc.get(scrub(party_type)))
 		if party_bank_account:
 			account = frappe.db.get_value("Bank Account", party_bank_account, "account")
-			bank = get_bank_cash_account(doc, account)
+			bank = get_bank_cash_account(doc, account, ignore_permissions=ignore_permissions)
 
 	paid_amount, received_amount = set_paid_amount_and_received_amount(
 		dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
@@ -2389,9 +2390,13 @@
 		pe.set(dimension, doc.get(dimension))
 
 
-def get_bank_cash_account(doc, bank_account):
+def get_bank_cash_account(doc, bank_account, ignore_permissions=False):
 	bank = get_default_bank_cash_account(
-		doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"), account=bank_account
+		doc.company,
+		"Bank",
+		mode_of_payment=doc.get("mode_of_payment"),
+		account=bank_account,
+		ignore_permissions=ignore_permissions,
 	)
 
 	if not bank:
diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py
index 0dcb179..60f288e 100644
--- a/erpnext/accounts/doctype/payment_order/test_payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py
@@ -4,9 +4,13 @@
 import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import getdate
 
-from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
+from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
+	create_bank_account,
+	create_gl_account,
+)
 from erpnext.accounts.doctype.payment_entry.payment_entry import (
 	get_payment_entry,
 	make_payment_order,
@@ -14,28 +18,32 @@
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 
 
-class TestPaymentOrder(unittest.TestCase):
+class TestPaymentOrder(FrappeTestCase):
 	def setUp(self):
-		create_bank_account()
+		# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
+		uniq_identifier = frappe.generate_hash(length=10)
+		self.gl_account = create_gl_account("_Test Bank " + uniq_identifier)
+		self.bank_account = create_bank_account(
+			gl_account=self.gl_account, bank_account_name="Checking Account " + uniq_identifier
+		)
 
 	def tearDown(self):
-		for bt in frappe.get_all("Payment Order"):
-			doc = frappe.get_doc("Payment Order", bt.name)
-			doc.cancel()
-			doc.delete()
+		frappe.db.rollback()
 
 	def test_payment_order_creation_against_payment_entry(self):
 		purchase_invoice = make_purchase_invoice()
 		payment_entry = get_payment_entry(
-			"Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC"
+			"Purchase Invoice", purchase_invoice.name, bank_account=self.gl_account
 		)
 		payment_entry.reference_no = "_Test_Payment_Order"
 		payment_entry.reference_date = getdate()
-		payment_entry.party_bank_account = "Checking Account - Citi Bank"
+		payment_entry.party_bank_account = self.bank_account
 		payment_entry.insert()
 		payment_entry.submit()
 
-		doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
+		doc = create_payment_order_against_payment_entry(
+			payment_entry, "Payment Entry", self.bank_account
+		)
 		reference_doc = doc.get("references")[0]
 		self.assertEqual(reference_doc.reference_name, payment_entry.name)
 		self.assertEqual(reference_doc.reference_doctype, "Payment Entry")
@@ -43,13 +51,13 @@
 		self.assertEqual(reference_doc.amount, 250)
 
 
-def create_payment_order_against_payment_entry(ref_doc, order_type):
+def create_payment_order_against_payment_entry(ref_doc, order_type, bank_account):
 	payment_order = frappe.get_doc(
 		dict(
 			doctype="Payment Order",
 			company="_Test Company",
 			payment_order_type=order_type,
-			company_bank_account="Checking Account - Citi Bank",
+			company_bank_account=bank_account,
 		)
 	)
 	doc = make_payment_order(ref_doc.name, payment_order)
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index d7a73f0..fb75a0f 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -591,6 +591,70 @@
 		self.assertEqual(si.status, "Paid")
 		self.assertEqual(si.outstanding_amount, 0)
 
+	def test_invoice_status_after_cr_note_cancellation(self):
+		# This test case is made after the 'always standalone Credit/Debit notes' feature is introduced
+		transaction_date = nowdate()
+		amount = 100
+
+		si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
+
+		cr_note = self.create_sales_invoice(
+			qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
+		)
+		cr_note.is_return = 1
+		cr_note.return_against = si.name
+		cr_note = cr_note.save().submit()
+
+		pr = self.create_payment_reconciliation()
+
+		pr.get_unreconciled_entries()
+		invoices = [x.as_dict() for x in pr.get("invoices")]
+		payments = [x.as_dict() for x in pr.get("payments")]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+
+		pr.get_unreconciled_entries()
+		self.assertEqual(pr.get("invoices"), [])
+		self.assertEqual(pr.get("payments"), [])
+
+		journals = frappe.db.get_all(
+			"Journal Entry",
+			filters={
+				"is_system_generated": 1,
+				"docstatus": 1,
+				"voucher_type": "Credit Note",
+				"reference_type": si.doctype,
+				"reference_name": si.name,
+			},
+			pluck="name",
+		)
+		self.assertEqual(len(journals), 1)
+
+		# assert status and outstanding
+		si.reload()
+		self.assertEqual(si.status, "Credit Note Issued")
+		self.assertEqual(si.outstanding_amount, 0)
+
+		cr_note.reload()
+		cr_note.cancel()
+		# 'Credit Note' Journal should be auto cancelled
+		journals = frappe.db.get_all(
+			"Journal Entry",
+			filters={
+				"is_system_generated": 1,
+				"docstatus": 1,
+				"voucher_type": "Credit Note",
+				"reference_type": si.doctype,
+				"reference_name": si.name,
+			},
+			pluck="name",
+		)
+		self.assertEqual(len(journals), 0)
+		# assert status and outstanding
+		si.reload()
+		self.assertEqual(si.status, "Unpaid")
+		self.assertEqual(si.outstanding_amount, 100)
+
 	def test_cr_note_partial_against_invoice(self):
 		transaction_date = nowdate()
 		amount = 100
diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
index 5a281aa..b92579e 100644
--- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
+++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
@@ -80,13 +80,16 @@
   "target_warehouse",
   "quality_inspection",
   "serial_and_batch_bundle",
-  "batch_no",
+  "use_serial_batch_fields",
   "col_break5",
   "allow_zero_valuation_rate",
-  "serial_no",
   "item_tax_rate",
   "actual_batch_qty",
   "actual_qty",
+  "section_break_tlhi",
+  "serial_no",
+  "column_break_ciit",
+  "batch_no",
   "edit_references",
   "sales_order",
   "so_detail",
@@ -628,13 +631,13 @@
    "options": "Quality Inspection"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "batch_no",
    "fieldtype": "Link",
    "hidden": 1,
    "label": "Batch No",
    "options": "Batch",
-   "print_hide": 1,
-   "read_only": 1
+   "print_hide": 1
   },
   {
    "fieldname": "col_break5",
@@ -649,14 +652,14 @@
    "print_hide": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "serial_no",
-   "fieldtype": "Small Text",
+   "fieldtype": "Text",
    "hidden": 1,
    "in_list_view": 1,
    "label": "Serial No",
    "oldfieldname": "serial_no",
-   "oldfieldtype": "Small Text",
-   "read_only": 1
+   "oldfieldtype": "Small Text"
   },
   {
    "fieldname": "item_tax_rate",
@@ -824,17 +827,33 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
    "no_copy": 1,
    "options": "Serial and Batch Bundle",
    "print_hide": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
+   "fieldname": "section_break_tlhi",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_ciit",
+   "fieldtype": "Column Break"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2023-11-14 18:33:22.585715",
+ "modified": "2024-02-25 15:50:17.140269",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Invoice Item",
diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py
index e2a62f1..c24db1d 100644
--- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py
+++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py
@@ -72,7 +72,7 @@
 		rate_with_margin: DF.Currency
 		sales_order: DF.Link | None
 		serial_and_batch_bundle: DF.Link | None
-		serial_no: DF.SmallText | None
+		serial_no: DF.Text | None
 		service_end_date: DF.Date | None
 		service_start_date: DF.Date | None
 		service_stop_date: DF.Date | None
@@ -82,6 +82,7 @@
 		target_warehouse: DF.Link | None
 		total_weight: DF.Float
 		uom: DF.Link
+		use_serial_batch_fields: DF.Check
 		warehouse: DF.Link | None
 		weight_per_unit: DF.Float
 		weight_uom: DF.Link | None
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index c4e09b4..c68ff83 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -696,6 +696,7 @@
 		# Updating stock ledger should always be called after updating prevdoc status,
 		# because updating ordered qty in bin depends upon updated ordered qty in PO
 		if self.update_stock == 1:
+			self.make_bundle_using_old_serial_batch_fields()
 			self.update_stock_ledger()
 
 			if self.is_old_subcontracting_flow:
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 26984d9..3ee4214 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -62,16 +62,19 @@
   "rm_supp_cost",
   "warehouse_section",
   "warehouse",
-  "from_warehouse",
-  "quality_inspection",
   "add_serial_batch_bundle",
   "serial_and_batch_bundle",
-  "serial_no",
+  "use_serial_batch_fields",
   "col_br_wh",
+  "from_warehouse",
+  "quality_inspection",
   "rejected_warehouse",
   "rejected_serial_and_batch_bundle",
-  "batch_no",
+  "section_break_rqbe",
+  "serial_no",
   "rejected_serial_no",
+  "column_break_vbbb",
+  "batch_no",
   "manufacture_details",
   "manufacturer",
   "column_break_13",
@@ -440,13 +443,11 @@
    "print_hide": 1
   },
   {
-   "depends_on": "eval:!doc.is_fixed_asset",
+   "depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
    "fieldname": "batch_no",
    "fieldtype": "Link",
-   "hidden": 1,
    "label": "Batch No",
    "options": "Batch",
-   "read_only": 1,
    "search_index": 1
   },
   {
@@ -454,21 +455,18 @@
    "fieldtype": "Column Break"
   },
   {
-   "depends_on": "eval:!doc.is_fixed_asset",
+   "depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
    "fieldname": "serial_no",
    "fieldtype": "Text",
-   "hidden": 1,
-   "label": "Serial No",
-   "read_only": 1
+   "label": "Serial No"
   },
   {
-   "depends_on": "eval:!doc.is_fixed_asset",
+   "depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
    "fieldname": "rejected_serial_no",
    "fieldtype": "Text",
    "label": "Rejected Serial No",
    "no_copy": 1,
-   "print_hide": 1,
-   "read_only": 1
+   "print_hide": 1
   },
   {
    "fieldname": "accounting",
@@ -891,7 +889,7 @@
    "label": "Apply TDS"
   },
   {
-   "depends_on": "eval:parent.update_stock == 1",
+   "depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
@@ -901,7 +899,7 @@
    "search_index": 1
   },
   {
-   "depends_on": "eval:parent.update_stock == 1",
+   "depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
    "fieldname": "rejected_serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Rejected Serial and Batch Bundle",
@@ -916,16 +914,31 @@
    "options": "Asset"
   },
   {
-   "depends_on": "eval:parent.update_stock === 1",
+   "depends_on": "eval:parent.update_stock === 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
    "fieldname": "add_serial_batch_bundle",
    "fieldtype": "Button",
    "label": "Add Serial / Batch No"
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
+   "fieldname": "section_break_rqbe",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_vbbb",
+   "fieldtype": "Column Break"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2024-01-21 19:46:25.537861",
+ "modified": "2024-02-04 14:11:52.742228",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py
index e48d223..ccbc347 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py
@@ -88,6 +88,7 @@
 		stock_uom_rate: DF.Currency
 		total_weight: DF.Float
 		uom: DF.Link
+		use_serial_batch_fields: DF.Check
 		valuation_rate: DF.Currency
 		warehouse: DF.Link | None
 		weight_per_unit: DF.Float
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index e900b81..ef1f6bd 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -14,23 +14,8 @@
 		super.setup(doc);
 	}
 	company() {
+		super.company();
 		erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
-
-		let me = this;
-		if (this.frm.doc.company) {
-			frappe.call({
-				method:
-					"erpnext.accounts.party.get_party_account",
-				args: {
-					party_type: 'Customer',
-					party: this.frm.doc.customer,
-					company: this.frm.doc.company
-				},
-				callback: (response) => {
-					if (response) me.frm.set_value("debit_to", response.message);
-				},
-			});
-		}
 	}
 	onload() {
 		var me = this;
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index c381b8a..4da1fe3 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -270,7 +270,7 @@
 		super(SalesInvoice, self).validate()
 		self.validate_auto_set_posting_time()
 
-		if not self.is_pos:
+		if not (self.is_pos or self.is_debit_note):
 			self.so_dn_required()
 
 		self.set_tax_withholding()
@@ -447,6 +447,11 @@
 		# Updating stock ledger should always be called after updating prevdoc status,
 		# because updating reserved qty in bin depends upon updated delivered qty in SO
 		if self.update_stock == 1:
+			for table_name in ["items", "packed_items"]:
+				if not self.get(table_name):
+					continue
+
+				self.make_bundle_using_old_serial_batch_fields(table_name)
 			self.update_stock_ledger()
 
 		# this sequence because outstanding may get -ve
@@ -1477,9 +1482,7 @@
 								"credit_in_account_currency": payment_mode.base_amount
 								if self.party_account_currency == self.company_currency
 								else payment_mode.amount,
-								"against_voucher": self.return_against
-								if cint(self.is_return) and self.return_against
-								else self.name,
+								"against_voucher": self.name,
 								"against_voucher_type": self.doctype,
 								"cost_center": self.cost_center,
 							},
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 8c3aede..c6a8362 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1105,6 +1105,44 @@
 		self.assertEqual(pos.grand_total, 100.0)
 		self.assertEqual(pos.write_off_amount, 10)
 
+	def test_ledger_entries_of_return_pos_invoice(self):
+		make_pos_profile()
+
+		pos = create_sales_invoice(do_not_save=True)
+		pos.is_pos = 1
+		pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
+		pos.save().submit()
+		self.assertEqual(pos.outstanding_amount, 0.0)
+		self.assertEqual(pos.status, "Paid")
+
+		from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
+
+		pos_return = make_sales_return(pos.name)
+		pos_return.save().submit()
+		pos_return.reload()
+		pos.reload()
+		self.assertEqual(pos_return.is_return, 1)
+		self.assertEqual(pos_return.return_against, pos.name)
+		self.assertEqual(pos_return.outstanding_amount, 0.0)
+		self.assertEqual(pos_return.status, "Return")
+		self.assertEqual(pos.outstanding_amount, 0.0)
+		self.assertEqual(pos.status, "Credit Note Issued")
+
+		expected = (
+			("Cash - _TC", 0.0, 100.0, pos_return.name, None),
+			("Debtors - _TC", 0.0, 100.0, pos_return.name, pos_return.name),
+			("Debtors - _TC", 100.0, 0.0, pos_return.name, pos_return.name),
+			("Sales - _TC", 100.0, 0.0, pos_return.name, None),
+		)
+		res = frappe.db.get_all(
+			"GL Entry",
+			filters={"voucher_no": pos_return.name, "is_cancelled": 0},
+			fields=["account", "debit", "credit", "voucher_no", "against_voucher"],
+			order_by="account, debit, credit",
+			as_list=1,
+		)
+		self.assertEqual(expected, res)
+
 	def test_pos_with_no_gl_entry_for_change_amount(self):
 		frappe.db.set_single_value("Accounts Settings", "post_change_gl_entries", 0)
 
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index ec9e792..e7536e9 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -83,14 +83,17 @@
   "quality_inspection",
   "pick_serial_and_batch",
   "serial_and_batch_bundle",
-  "batch_no",
-  "incoming_rate",
+  "use_serial_batch_fields",
   "col_break5",
   "allow_zero_valuation_rate",
-  "serial_no",
+  "incoming_rate",
   "item_tax_rate",
   "actual_batch_qty",
   "actual_qty",
+  "section_break_eoec",
+  "serial_no",
+  "column_break_ytgd",
+  "batch_no",
   "edit_references",
   "sales_order",
   "so_detail",
@@ -600,12 +603,11 @@
    "options": "Quality Inspection"
   },
   {
+   "depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
    "fieldname": "batch_no",
    "fieldtype": "Link",
-   "hidden": 1,
    "label": "Batch No",
    "options": "Batch",
-   "read_only": 1,
    "search_index": 1
   },
   {
@@ -621,13 +623,12 @@
    "print_hide": 1
   },
   {
+   "depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
    "fieldname": "serial_no",
-   "fieldtype": "Small Text",
-   "hidden": 1,
+   "fieldtype": "Text",
    "label": "Serial No",
    "oldfieldname": "serial_no",
-   "oldfieldtype": "Small Text",
-   "read_only": 1
+   "oldfieldtype": "Small Text"
   },
   {
    "fieldname": "item_group",
@@ -891,6 +892,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
@@ -904,12 +906,27 @@
    "fieldname": "pick_serial_and_batch",
    "fieldtype": "Button",
    "label": "Pick Serial / Batch No"
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
+   "fieldname": "section_break_eoec",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_ytgd",
+   "fieldtype": "Column Break"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-12-29 13:03:14.121298",
+ "modified": "2024-02-25 15:56:44.828634",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Item",
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 80f6774..9be1b42 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py
@@ -75,7 +75,7 @@
 		sales_invoice_item: DF.Data | None
 		sales_order: DF.Link | None
 		serial_and_batch_bundle: DF.Link | None
-		serial_no: DF.SmallText | None
+		serial_no: DF.Text | None
 		service_end_date: DF.Date | None
 		service_start_date: DF.Date | None
 		service_stop_date: DF.Date | None
@@ -86,6 +86,7 @@
 		target_warehouse: DF.Link | None
 		total_weight: DF.Float
 		uom: DF.Link
+		use_serial_batch_fields: DF.Check
 		warehouse: DF.Link | None
 		weight_per_unit: DF.Float
 		weight_uom: DF.Link | None
diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py
index f404d99..57f66dd 100644
--- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py
+++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py
@@ -8,6 +8,7 @@
 from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
 from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
+from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
 
 
 class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
@@ -49,6 +50,16 @@
 		)
 		return pe
 
+	def create_sales_order(self):
+		so = make_sales_order(
+			company=self.company,
+			customer=self.customer,
+			item=self.item,
+			rate=100,
+			transaction_date=today(),
+		)
+		return so
+
 	def test_01_unreconcile_invoice(self):
 		si1 = self.create_sales_invoice()
 		si2 = self.create_sales_invoice()
@@ -314,3 +325,41 @@
 			),
 			1,
 		)
+
+	def test_05_unreconcile_order(self):
+		so = self.create_sales_order()
+
+		pe = self.create_payment_entry()
+		# Allocation payment against Sales Order
+		pe.paid_amount = 100
+		pe.append(
+			"references",
+			{"reference_doctype": so.doctype, "reference_name": so.name, "allocated_amount": 100},
+		)
+		pe.save().submit()
+
+		# Assert 'Advance Paid'
+		so.reload()
+		self.assertEqual(so.advance_paid, 100)
+
+		unreconcile = frappe.get_doc(
+			{
+				"doctype": "Unreconcile Payment",
+				"company": self.company,
+				"voucher_type": pe.doctype,
+				"voucher_no": pe.name,
+			}
+		)
+		unreconcile.add_references()
+		self.assertEqual(len(unreconcile.allocations), 1)
+		allocations = [x.reference_name for x in unreconcile.allocations]
+		self.assertEquals([so.name], allocations)
+		# unreconcile so
+		unreconcile.save().submit()
+
+		# Assert 'Advance Paid'
+		so.reload()
+		pe.reload()
+		self.assertEqual(so.advance_paid, 0)
+		self.assertEqual(len(pe.references), 0)
+		self.assertEqual(pe.unallocated_amount, 100)
diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
index 9b56952..664622f 100644
--- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
+++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
@@ -82,6 +82,11 @@
 			update_voucher_outstanding(
 				alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party
 			)
+			if doc.doctype in frappe.get_hooks("advance_payment_payable_doctypes") + frappe.get_hooks(
+				"advance_payment_receivable_doctypes"
+			):
+				doc.set_total_advance_paid()
+
 			frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
 
 
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 1c8ac2f..2e82886 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -13,9 +13,13 @@
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_accounting_dimensions,
 )
+from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
+	get_dimension_filter_map,
+)
 from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
 from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
 from erpnext.accounts.utils import create_payment_ledger_entry
+from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
 
 
 def make_gl_entries(
@@ -355,6 +359,7 @@
 
 	process_debit_credit_difference(gl_map)
 
+	dimension_filter_map = get_dimension_filter_map()
 	if gl_map:
 		check_freezing_date(gl_map[0]["posting_date"], adv_adj)
 		is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
@@ -362,6 +367,7 @@
 			validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
 
 	for entry in gl_map:
+		validate_allowed_dimensions(entry, dimension_filter_map)
 		make_entry(entry, adv_adj, update_outstanding, from_repost)
 
 
@@ -700,3 +706,39 @@
 		where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
 		(now(), frappe.session.user, voucher_type, voucher_no),
 	)
+
+
+def validate_allowed_dimensions(gl_entry, dimension_filter_map):
+	for key, value in dimension_filter_map.items():
+		dimension = key[0]
+		account = key[1]
+
+		if gl_entry.account == account:
+			if value["is_mandatory"] and not gl_entry.get(dimension):
+				frappe.throw(
+					_("{0} is mandatory for account {1}").format(
+						frappe.bold(frappe.unscrub(dimension)), frappe.bold(gl_entry.account)
+					),
+					MandatoryAccountDimensionError,
+				)
+
+			if value["allow_or_restrict"] == "Allow":
+				if gl_entry.get(dimension) and gl_entry.get(dimension) not in value["allowed_dimensions"]:
+					frappe.throw(
+						_("Invalid value {0} for {1} against account {2}").format(
+							frappe.bold(gl_entry.get(dimension)),
+							frappe.bold(frappe.unscrub(dimension)),
+							frappe.bold(gl_entry.account),
+						),
+						InvalidAccountDimensionError,
+					)
+			else:
+				if gl_entry.get(dimension) and gl_entry.get(dimension) in value["allowed_dimensions"]:
+					frappe.throw(
+						_("Invalid value {0} for {1} against account {2}").format(
+							frappe.bold(gl_entry.get(dimension)),
+							frappe.bold(frappe.unscrub(dimension)),
+							frappe.bold(gl_entry.account),
+						),
+						InvalidAccountDimensionError,
+					)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index fc9034b..d8ae2a4 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -9,7 +9,7 @@
 from frappe.contacts.doctype.address.address import get_company_address, get_default_address
 from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
 from frappe.model.utils import get_fetch_values
-from frappe.query_builder.functions import Abs, Date, Sum
+from frappe.query_builder.functions import Abs, Count, Date, Sum
 from frappe.utils import (
 	add_days,
 	add_months,
@@ -784,34 +784,37 @@
 	from frappe.desk.form.load import get_communication_data
 
 	out = {}
-	fields = "creation, count(*)"
 	after = add_years(None, -1).strftime("%Y-%m-%d")
-	group_by = "group by Date(creation)"
 
 	data = get_communication_data(
 		doctype,
 		name,
 		after=after,
-		group_by="group by creation",
-		fields="C.creation as creation, count(C.name)",
+		group_by="group by communication_date",
+		fields="C.communication_date as communication_date, count(C.name)",
 		as_dict=False,
 	)
 
 	# fetch and append data from Activity Log
-	data += frappe.db.sql(
-		"""select {fields}
-		from `tabActivity Log`
-		where (reference_doctype=%(doctype)s and reference_name=%(name)s)
-		or (timeline_doctype in (%(doctype)s) and timeline_name=%(name)s)
-		or (reference_doctype in ("Quotation", "Opportunity") and timeline_name=%(name)s)
-		and status!='Success' and creation > {after}
-		{group_by} order by creation desc
-		""".format(
-			fields=fields, group_by=group_by, after=after
-		),
-		{"doctype": doctype, "name": name},
-		as_dict=False,
-	)
+	activity_log = frappe.qb.DocType("Activity Log")
+	data += (
+		frappe.qb.from_(activity_log)
+		.select(activity_log.communication_date, Count(activity_log.name))
+		.where(
+			(
+				((activity_log.reference_doctype == doctype) & (activity_log.reference_name == name))
+				| ((activity_log.timeline_doctype == doctype) & (activity_log.timeline_name == name))
+				| (
+					(activity_log.reference_doctype.isin(["Quotation", "Opportunity"]))
+					& (activity_log.timeline_name == name)
+				)
+			)
+			& (activity_log.status != "Success")
+			& (activity_log.creation > after)
+		)
+		.groupby(activity_log.communication_date)
+		.orderby(activity_log.communication_date, order=frappe.qb.desc)
+	).run()
 
 	timeline_items = dict(data)
 
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index e3fa5e8..6d77ef5 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -83,7 +83,10 @@
 			self.skip_total_row = 1
 
 		if self.filters.get("in_party_currency"):
-			self.skip_total_row = 1
+			if self.filters.get("party") and len(self.filters.get("party")) == 1:
+				self.skip_total_row = 0
+			else:
+				self.skip_total_row = 1
 
 	def get_data(self):
 		self.get_ple_entries()
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index e4efefe..7162aef 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -975,7 +975,7 @@
 						& (sle.is_cancelled == 0)
 					)
 					.orderby(sle.item_code)
-					.orderby(sle.warehouse, sle.posting_date, sle.posting_time, sle.creation, order=Order.desc)
+					.orderby(sle.warehouse, sle.posting_datetime, sle.creation, order=Order.desc)
 					.run(as_dict=True)
 				)
 
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index 56ae41a..a4f01fa 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -357,7 +357,13 @@
 				and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
 
 	if filters.get("warehouse"):
-		conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
+		if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
+			lft, rgt = frappe.db.get_all(
+				"Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True
+			)[0]
+			conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) "
+		else:
+			conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
 
 	if filters.get("brand"):
 		conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
index 3f178f4..eaeaa62 100644
--- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
+++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
@@ -163,7 +163,7 @@
 		"""select
 		voucher_type, voucher_no, party_type, party, posting_date, debit, credit, remarks, against_voucher
 		from `tabGL Entry`
-		where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') {0}
+		where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') and is_cancelled = 0 {0}
 	""".format(
 			get_conditions(filters)
 		),
diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
index 4a80dd0..b18570b 100644
--- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
@@ -63,16 +63,14 @@
 				tax_amount += entry.credit - entry.debit
 				# infer tax withholding category from the account if it's the single account for this category
 				tax_withholding_category = tds_accounts.get(entry.account)
-				rate = tax_rate_map.get(tax_withholding_category)
 				# or else the consolidated value from the voucher document
 				if not tax_withholding_category:
-					# or else from the party default
 					tax_withholding_category = tax_category_map.get(name)
-					rate = tax_rate_map.get(tax_withholding_category)
+				# or else from the party default
 				if not tax_withholding_category:
 					tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
-					rate = tax_rate_map.get(tax_withholding_category)
 
+				rate = tax_rate_map.get(tax_withholding_category)
 			if net_total_map.get(name):
 				if voucher_type == "Journal Entry" and tax_amount and rate:
 					# back calcalute total amount from rate and tax_amount
@@ -244,7 +242,7 @@
 				"width": 120,
 			},
 			{
-				"label": _("Tax Amount"),
+				"label": _("TDS Amount") if filters.get("party_type") == "Supplier" else _("TCS Amount"),
 				"fieldname": "tax_amount",
 				"fieldtype": "Float",
 				"width": 120,
@@ -295,7 +293,7 @@
 	tds_accounts = {}
 	for tds_acc in _tds_accounts:
 		# if it turns out not to be the only tax withholding category, then don't include in the map
-		if tds_accounts.get(tds_acc["account"]):
+		if tds_acc["account"] in tds_accounts:
 			tds_accounts[tds_acc["account"]] = None
 		else:
 			tds_accounts[tds_acc["account"]] = tds_acc["parent"]
@@ -354,9 +352,6 @@
 	if filters.get("to_date"):
 		query = query.where(gle.posting_date <= filters.get("to_date"))
 
-	if bank_accounts:
-		query = query.where(gle.against.notin(bank_accounts))
-
 	if filters.get("party"):
 		party = [filters.get("party")]
 		jv_condition = gle.against.isin(party) | (
@@ -368,7 +363,14 @@
 			(gle.voucher_type == "Journal Entry")
 			& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
 		)
-	query = query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party))
+
+	query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party))
+	if bank_accounts:
+		query = query.where(
+			gle.against.notin(bank_accounts) & (gle.account.isin(tds_accounts) & jv_condition)
+			| gle.party.isin(party)
+		)
+
 	return query
 
 
@@ -408,7 +410,7 @@
 			"paid_amount_after_tax",
 			"base_paid_amount",
 		],
-		"Journal Entry": ["tax_withholding_category", "total_amount"],
+		"Journal Entry": ["total_amount"],
 	}
 
 	entries = frappe.get_all(
diff --git a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
index b3f6737..7515616 100644
--- a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
@@ -5,7 +5,6 @@
 from frappe.tests.utils import FrappeTestCase
 from frappe.utils import today
 
-from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
 from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -17,36 +16,63 @@
 from erpnext.accounts.utils import get_fiscal_year
 
 
-class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
+class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
 	def setUp(self):
 		self.create_company()
 		self.clear_old_entries()
 		create_tax_accounts()
-		create_tcs_category()
 
 	def test_tax_withholding_for_customers(self):
+		create_tax_category(cumulative_threshold=300)
+		frappe.db.set_value("Customer", "_Test Customer", "tax_withholding_category", "TCS")
 		si = create_sales_invoice(rate=1000)
 		pe = create_tcs_payment_entry()
+		jv = create_tcs_journal_entry()
+
 		filters = frappe._dict(
 			company="_Test Company", party_type="Customer", from_date=today(), to_date=today()
 		)
 		result = execute(filters)[1]
 		expected_values = [
+			# Check for JV totals using back calculation logic
+			[jv.name, "TCS", 0.075, -10000.0, -7.5, -10000.0],
 			[pe.name, "TCS", 0.075, 2550, 0.53, 2550.53],
 			[si.name, "TCS", 0.075, 1000, 0.52, 1000.52],
 		]
 		self.check_expected_values(result, expected_values)
 
+	def test_single_account_for_multiple_categories(self):
+		create_tax_category("TDS - 1", rate=10, account="TDS - _TC")
+		inv_1 = make_purchase_invoice(rate=1000, do_not_submit=True)
+		inv_1.tax_withholding_category = "TDS - 1"
+		inv_1.submit()
+
+		create_tax_category("TDS - 2", rate=20, account="TDS - _TC")
+		inv_2 = make_purchase_invoice(rate=1000, do_not_submit=True)
+		inv_2.tax_withholding_category = "TDS - 2"
+		inv_2.submit()
+		result = execute(
+			frappe._dict(company="_Test Company", party_type="Supplier", from_date=today(), to_date=today())
+		)[1]
+		expected_values = [
+			[inv_1.name, "TDS - 1", 10, 5000, 500, 5500],
+			[inv_2.name, "TDS - 2", 20, 5000, 1000, 6000],
+		]
+		self.check_expected_values(result, expected_values)
+
 	def check_expected_values(self, result, expected_values):
 		for i in range(len(result)):
 			voucher = frappe._dict(result[i])
 			voucher_expected_values = expected_values[i]
-			self.assertEqual(voucher.ref_no, voucher_expected_values[0])
-			self.assertEqual(voucher.section_code, voucher_expected_values[1])
-			self.assertEqual(voucher.rate, voucher_expected_values[2])
-			self.assertEqual(voucher.base_total, voucher_expected_values[3])
-			self.assertAlmostEqual(voucher.tax_amount, voucher_expected_values[4])
-			self.assertAlmostEqual(voucher.grand_total, voucher_expected_values[5])
+			voucher_actual_values = (
+				voucher.ref_no,
+				voucher.section_code,
+				voucher.rate,
+				voucher.base_total,
+				voucher.tax_amount,
+				voucher.grand_total,
+			)
+			self.assertSequenceEqual(voucher_actual_values, voucher_expected_values)
 
 	def tearDown(self):
 		self.clear_old_entries()
@@ -67,24 +93,20 @@
 		).insert(ignore_if_duplicate=True)
 
 
-def create_tcs_category():
+def create_tax_category(category="TCS", rate=0.075, account="TCS - _TC", cumulative_threshold=0):
 	fiscal_year = get_fiscal_year(today(), company="_Test Company")
 	from_date = fiscal_year[1]
 	to_date = fiscal_year[2]
 
-	tax_category = create_tax_withholding_category(
-		category_name="TCS",
-		rate=0.075,
+	create_tax_withholding_category(
+		category_name=category,
+		rate=rate,
 		from_date=from_date,
 		to_date=to_date,
-		account="TCS - _TC",
-		cumulative_threshold=300,
+		account=account,
+		cumulative_threshold=cumulative_threshold,
 	)
 
-	customer = frappe.get_doc("Customer", "_Test Customer")
-	customer.tax_withholding_category = "TCS"
-	customer.save()
-
 
 def create_tcs_payment_entry():
 	payment_entry = create_payment_entry(
@@ -109,3 +131,32 @@
 	)
 	payment_entry.submit()
 	return payment_entry
+
+
+def create_tcs_journal_entry():
+	jv = frappe.new_doc("Journal Entry")
+	jv.posting_date = today()
+	jv.company = "_Test Company"
+	jv.set(
+		"accounts",
+		[
+			{
+				"account": "Debtors - _TC",
+				"party_type": "Customer",
+				"party": "_Test Customer",
+				"credit_in_account_currency": 10000,
+			},
+			{
+				"account": "Debtors - _TC",
+				"party_type": "Customer",
+				"party": "_Test Customer",
+				"debit_in_account_currency": 9992.5,
+			},
+			{
+				"account": "TCS - _TC",
+				"debit_in_account_currency": 7.5,
+			},
+		],
+	)
+	jv.insert()
+	return jv.submit()
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js
index 2c4c762..5374ac1 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.js
+++ b/erpnext/accounts/report/trial_balance/trial_balance.js
@@ -78,8 +78,14 @@
 			"options": erpnext.get_presentation_currency_list()
 		},
 		{
-			"fieldname": "with_period_closing_entry",
-			"label": __("Period Closing Entry"),
+			"fieldname": "with_period_closing_entry_for_opening",
+			"label": __("With Period Closing Entry For Opening Balances"),
+			"fieldtype": "Check",
+			"default": 1
+		},
+		{
+			"fieldname": "with_period_closing_entry_for_current_period",
+			"label": __("Period Closing Entry For Current Period"),
 			"fieldtype": "Check",
 			"default": 1
 		},
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 8b7f0bb..2ff0eff 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -116,7 +116,7 @@
 		max_rgt,
 		filters,
 		gl_entries_by_account,
-		ignore_closing_entries=not flt(filters.with_period_closing_entry),
+		ignore_closing_entries=not flt(filters.with_period_closing_entry_for_current_period),
 		ignore_opening_entries=True,
 	)
 
@@ -249,7 +249,7 @@
 	):
 		opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
 
-	if not flt(filters.with_period_closing_entry):
+	if not flt(filters.with_period_closing_entry_for_opening):
 		if doctype == "Account Closing Balance":
 			opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
 		else:
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 64bc39a..157cfdd 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -982,46 +982,6 @@
 	return precision
 
 
-def get_stock_rbnb_difference(posting_date, company):
-	stock_items = frappe.db.sql_list(
-		"""select distinct item_code
-		from `tabStock Ledger Entry` where company=%s""",
-		company,
-	)
-
-	pr_valuation_amount = frappe.db.sql(
-		"""
-		select sum(pr_item.valuation_rate * pr_item.qty * pr_item.conversion_factor)
-		from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
-		where pr.name = pr_item.parent and pr.docstatus=1 and pr.company=%s
-		and pr.posting_date <= %s and pr_item.item_code in (%s)"""
-		% ("%s", "%s", ", ".join(["%s"] * len(stock_items))),
-		tuple([company, posting_date] + stock_items),
-	)[0][0]
-
-	pi_valuation_amount = frappe.db.sql(
-		"""
-		select sum(pi_item.valuation_rate * pi_item.qty * pi_item.conversion_factor)
-		from `tabPurchase Invoice Item` pi_item, `tabPurchase Invoice` pi
-		where pi.name = pi_item.parent and pi.docstatus=1 and pi.company=%s
-		and pi.posting_date <= %s and pi_item.item_code in (%s)"""
-		% ("%s", "%s", ", ".join(["%s"] * len(stock_items))),
-		tuple([company, posting_date] + stock_items),
-	)[0][0]
-
-	# Balance should be
-	stock_rbnb = flt(pr_valuation_amount, 2) - flt(pi_valuation_amount, 2)
-
-	# Balance as per system
-	stock_rbnb_account = "Stock Received But Not Billed - " + frappe.get_cached_value(
-		"Company", company, "abbr"
-	)
-	sys_bal = get_balance_on(stock_rbnb_account, posting_date, in_account_currency=False)
-
-	# Amount should be credited
-	return flt(stock_rbnb) + flt(sys_bal)
-
-
 def get_held_invoices(party_type, party):
 	"""
 	Returns a list of names Purchase Invoices for the given party that are on hold
@@ -1428,8 +1388,7 @@
 		.select(sle.voucher_type, sle.voucher_no, sle.posting_date, sle.posting_time, sle.creation)
 		.where((sle.is_cancelled == 0) & (sle.voucher_no.isin(voucher_nos)))
 		.groupby(sle.voucher_type, sle.voucher_no)
-		.orderby(sle.posting_date)
-		.orderby(sle.posting_time)
+		.orderby(sle.posting_datetime)
 		.orderby(sle.creation)
 	).run(as_dict=True)
 	sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles]
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index df4593b..191675c 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -561,15 +561,14 @@
 def reverse_depreciation_entry_made_after_disposal(asset, date):
 	for row in asset.get("finance_books"):
 		asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
-		if not asset_depr_schedule_doc:
+		if not asset_depr_schedule_doc or not asset_depr_schedule_doc.get("depreciation_schedule"):
 			continue
 
 		for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
-			if schedule.schedule_date == date:
+			if schedule.schedule_date == date and schedule.journal_entry:
 				if not disposal_was_made_on_original_schedule_date(
 					schedule_idx, row, date
 				) or disposal_happens_in_the_future(date):
-
 					reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
 					reverse_journal_entry.posting_date = nowdate()
 
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js
index 2f0de97..110f2c4 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js
@@ -6,7 +6,7 @@
 
 erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.stock.StockController {
 	setup() {
-		this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle'];
+		this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle', 'Asset Movement'];
 		this.setup_posting_date_time_check();
 	}
 
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index 5e251a5..e27a492 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -126,6 +126,7 @@
 		self.create_target_asset()
 
 	def on_submit(self):
+		self.make_bundle_using_old_serial_batch_fields()
 		self.update_stock_ledger()
 		self.make_gl_entries()
 		self.update_target_asset()
@@ -137,6 +138,7 @@
 			"Repost Item Valuation",
 			"Serial and Batch Bundle",
 			"Asset",
+			"Asset Movement",
 		)
 		self.cancel_target_asset()
 		self.update_stock_ledger()
@@ -146,7 +148,7 @@
 	def cancel_target_asset(self):
 		if self.entry_type == "Capitalization" and self.target_asset:
 			asset_doc = frappe.get_doc("Asset", self.target_asset)
-			frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
+			asset_doc.db_set("capitalized_in", None)
 			if asset_doc.docstatus == 1:
 				asset_doc.cancel()
 
diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
index 26e1c3c..f79a848 100644
--- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
+++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
@@ -18,9 +18,12 @@
   "amount",
   "batch_and_serial_no_section",
   "serial_and_batch_bundle",
+  "use_serial_batch_fields",
   "column_break_13",
-  "batch_no",
+  "section_break_bfqc",
   "serial_no",
+  "column_break_mbuv",
+  "batch_no",
   "accounting_dimensions_section",
   "cost_center",
   "dimension_col_break"
@@ -39,13 +42,13 @@
    "reqd": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "batch_no",
    "fieldtype": "Link",
    "label": "Batch No",
    "no_copy": 1,
    "options": "Batch",
-   "print_hide": 1,
-   "read_only": 1
+   "print_hide": 1
   },
   {
    "fieldname": "section_break_6",
@@ -102,12 +105,12 @@
    "fieldtype": "Column Break"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "serial_no",
-   "fieldtype": "Small Text",
+   "fieldtype": "Text",
    "hidden": 1,
    "label": "Serial No",
-   "print_hide": 1,
-   "read_only": 1
+   "print_hide": 1
   },
   {
    "fieldname": "item_code",
@@ -148,18 +151,34 @@
    "fieldtype": "Column Break"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
    "no_copy": 1,
    "options": "Serial and Batch Bundle",
    "print_hide": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
+   "fieldname": "section_break_bfqc",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_mbuv",
+   "fieldtype": "Column Break"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-04-06 01:10:17.947952",
+ "modified": "2024-02-25 15:57:35.007501",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset Capitalization Stock Item",
diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py
index 122cbb6..0f06cc7 100644
--- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py
+++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py
@@ -24,9 +24,10 @@
 		parentfield: DF.Data
 		parenttype: DF.Data
 		serial_and_batch_bundle: DF.Link | None
-		serial_no: DF.SmallText | None
+		serial_no: DF.Text | None
 		stock_qty: DF.Float
 		stock_uom: DF.Link
+		use_serial_batch_fields: DF.Check
 		valuation_rate: DF.Currency
 		warehouse: DF.Link
 	# end: auto-generated types
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 146c03e..77469df 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -418,14 +418,13 @@
 			)
 
 			# Adjust depreciation amount in the last period based on the expected value after useful life
-			if row.expected_value_after_useful_life and (
-				(
-					n == cint(final_number_of_depreciations) - 1
-					and value_after_depreciation != row.expected_value_after_useful_life
+			if (
+				n == cint(final_number_of_depreciations) - 1
+				and flt(value_after_depreciation) != flt(row.expected_value_after_useful_life)
+			) or flt(value_after_depreciation) < flt(row.expected_value_after_useful_life):
+				depreciation_amount += flt(value_after_depreciation) - flt(
+					row.expected_value_after_useful_life
 				)
-				or value_after_depreciation < row.expected_value_after_useful_life
-			):
-				depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
 				skip_row = True
 
 			if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) > 0:
@@ -813,15 +812,11 @@
 	asset_depr_schedules_names = []
 
 	for row in asset_doc.get("finance_books"):
-		draft_asset_depr_schedule_name = get_asset_depr_schedule_name(
-			asset_doc.name, "Draft", row.finance_book
+		asset_depr_schedule = get_asset_depr_schedule_name(
+			asset_doc.name, ["Draft", "Active"], row.finance_book
 		)
 
-		active_asset_depr_schedule_name = get_asset_depr_schedule_name(
-			asset_doc.name, "Active", row.finance_book
-		)
-
-		if not draft_asset_depr_schedule_name and not active_asset_depr_schedule_name:
+		if not asset_depr_schedule:
 			name = make_draft_asset_depr_schedule(asset_doc, row)
 			asset_depr_schedules_names.append(name)
 
@@ -997,16 +992,20 @@
 
 
 def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
-	finance_book_filter = ["finance_book", "is", "not set"]
-	if finance_book:
+	if finance_book is None:
+		finance_book_filter = ["finance_book", "is", "not set"]
+	else:
 		finance_book_filter = ["finance_book", "=", finance_book]
 
+	if isinstance(status, str):
+		status = [status]
+
 	return frappe.db.get_value(
 		doctype="Asset Depreciation Schedule",
 		filters=[
 			["asset", "=", asset_name],
 			finance_book_filter,
-			["status", "=", status],
+			["status", "in", status],
 		],
 	)
 
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 4efbb27..4d94868 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -457,6 +457,7 @@
 		self.update_ordered_qty()
 		self.update_reserved_qty_for_subcontract()
 		self.update_subcontracting_order_status()
+		self.update_blanket_order()
 		self.notify_update()
 		clear_doctype_notifications(self)
 
@@ -644,6 +645,7 @@
 				update_sco_status(sco, "Closed" if self.status == "Closed" else None)
 
 
+@frappe.request_cache
 def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
 	"""get last purchase rate for an item"""
 
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 5405799..a30de68 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -822,6 +822,30 @@
 		# To test if the PO does NOT have a Blanket Order
 		self.assertEqual(po_doc.items[0].blanket_order, None)
 
+	def test_blanket_order_on_po_close_and_open(self):
+		# Step - 1: Create Blanket Order
+		bo = make_blanket_order(blanket_order_type="Purchasing", quantity=10, rate=10)
+
+		# Step - 2: Create Purchase Order
+		po = create_purchase_order(
+			item_code="_Test Item", qty=5, against_blanket_order=1, against_blanket=bo.name
+		)
+
+		bo.load_from_db()
+		self.assertEqual(bo.items[0].ordered_qty, 5)
+
+		# Step - 3: Close Purchase Order
+		po.update_status("Closed")
+
+		bo.load_from_db()
+		self.assertEqual(bo.items[0].ordered_qty, 0)
+
+		# Step - 4: Re-Open Purchase Order
+		po.update_status("Re-open")
+
+		bo.load_from_db()
+		self.assertEqual(bo.items[0].ordered_qty, 5)
+
 	def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
 		from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
 			create_payment_terms_template,
@@ -1148,6 +1172,7 @@
 				"schedule_date": add_days(nowdate(), 1),
 				"include_exploded_items": args.get("include_exploded_items", 1),
 				"against_blanket_order": args.against_blanket_order,
+				"against_blanket": args.against_blanket,
 				"material_request": args.material_request,
 				"material_request_item": args.material_request_item,
 			},
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 5a24cc2..e3e8def 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -545,7 +545,6 @@
    "fieldname": "blanket_order",
    "fieldtype": "Link",
    "label": "Blanket Order",
-   "no_copy": 1,
    "options": "Blanket Order"
   },
   {
@@ -553,7 +552,6 @@
    "fieldname": "blanket_order_rate",
    "fieldtype": "Currency",
    "label": "Blanket Order Rate",
-   "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
   },
@@ -917,7 +915,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-24 13:24:41.298416",
+ "modified": "2024-02-05 11:23:24.859435",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order Item",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index a0569ec..32a5a61 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -216,6 +216,19 @@
 					)
 				)
 
+			if self.get("is_return") and self.get("return_against") and not self.get("is_pos"):
+				# if self.get("is_return") and self.get("return_against"):
+				document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
+				frappe.msgprint(
+					_(
+						"{0} will be treated as a standalone {0}. Post creation use {1} tool to reconcile against {2}."
+					).format(
+						document_type,
+						get_link_to_form("Payment Reconciliation"),
+						get_link_to_form(self.doctype, self.get("return_against")),
+					)
+				)
+
 			pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
 			if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
 				self.set_advances()
@@ -333,6 +346,12 @@
 			ple = frappe.qb.DocType("Payment Ledger Entry")
 			frappe.qb.from_(ple).delete().where(
 				(ple.voucher_type == self.doctype) & (ple.voucher_no == self.name)
+				| (
+					(ple.against_voucher_type == self.doctype)
+					& (ple.against_voucher_no == self.name)
+					& ple.delinked
+					== 1
+				)
 			).run()
 			frappe.db.sql(
 				"delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
@@ -693,7 +712,7 @@
 					if self.get("is_subcontracted"):
 						args["is_subcontracted"] = self.is_subcontracted
 
-					ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
+					ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
 
 					for fieldname, value in ret.items():
 						if item.meta.get_field(fieldname) and value is not None:
@@ -1476,6 +1495,24 @@
 						x.update({dim.fieldname: self.get(dim.fieldname)})
 			reconcile_against_document(lst, active_dimensions=active_dimensions)
 
+	def cancel_system_generated_credit_debit_notes(self):
+		# Cancel 'Credit/Debit' Note Journal Entries, if found.
+		if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
+			voucher_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
+			journals = frappe.db.get_all(
+				"Journal Entry",
+				filters={
+					"is_system_generated": 1,
+					"reference_type": self.doctype,
+					"reference_name": self.name,
+					"voucher_type": voucher_type,
+					"docstatus": 1,
+				},
+				pluck="name",
+			)
+			for x in journals:
+				frappe.get_doc("Journal Entry", x).cancel()
+
 	def on_cancel(self):
 		from erpnext.accounts.doctype.bank_transaction.bank_transaction import (
 			remove_from_bank_transaction,
@@ -1488,6 +1525,8 @@
 		remove_from_bank_transaction(self.doctype, self.name)
 
 		if self.doctype in ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]:
+			self.cancel_system_generated_credit_debit_notes()
+
 			# Cancel Exchange Gain/Loss Journal before unlinking
 			cancel_exchange_gain_loss_journal(self)
 
@@ -2671,7 +2710,7 @@
 
 	if order_list:
 		q = q.where(
-			(journal_acc.reference_type == order_doctype) & ((journal_acc.reference_type).isin(order_list))
+			(journal_acc.reference_type == order_doctype) & ((journal_acc.reference_name).isin(order_list))
 		)
 
 	q = q.orderby(journal_entry.posting_date)
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index fb68010..91ee53a 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -217,8 +217,8 @@
 			lc_voucher_data = frappe.db.sql(
 				"""select sum(applicable_charges), cost_center
 				from `tabLanded Cost Item`
-				where docstatus = 1 and purchase_receipt_item = %s""",
-				d.name,
+				where docstatus = 1 and purchase_receipt_item = %s and receipt_document = %s""",
+				(d.name, self.name),
 			)
 			d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0
 			if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:
@@ -824,7 +824,8 @@
 		if self.doctype == "Purchase Invoice" and not self.get("update_stock"):
 			return
 
-		frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name)
+		asset_movement = frappe.db.get_value("Asset Movement", {"reference_name": self.name}, "name")
+		frappe.delete_doc("Asset Movement", asset_movement, force=1)
 
 	def validate_schedule_date(self):
 		if not self.get("items"):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index e234eec..c46ef50 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -729,17 +729,24 @@
 	conditions, bin_conditions = [], []
 	filter_dict = get_doctype_wise_filters(filters)
 
-	query = """select `tabWarehouse`.name,
+	warehouse_field = "name"
+	meta = frappe.get_meta("Warehouse")
+	if meta.get("show_title_field_in_link") and meta.get("title_field"):
+		searchfield = meta.get("title_field")
+		warehouse_field = meta.get("title_field")
+
+	query = """select `tabWarehouse`.`{warehouse_field}`,
 		CONCAT_WS(' : ', 'Actual Qty', ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty
 		from `tabWarehouse` left join `tabBin`
 		on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions}
 		where
 			`tabWarehouse`.`{key}` like {txt}
 			{fcond} {mcond}
-		order by ifnull(`tabBin`.actual_qty, 0) desc
+		order by ifnull(`tabBin`.actual_qty, 0) desc, `tabWarehouse`.`{warehouse_field}` asc
 		limit
 			{page_len} offset {start}
 		""".format(
+		warehouse_field=warehouse_field,
 		bin_conditions=get_filters_cond(
 			doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True
 		),
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 8c43842..359d721 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -28,7 +28,8 @@
 	def validate(self):
 		super(SellingController, self).validate()
 		self.validate_items()
-		self.validate_max_discount()
+		if not self.get("is_debit_note"):
+			self.validate_max_discount()
 		self.validate_selling_price()
 		self.set_qty_as_per_stock_uom()
 		self.set_po_nos(for_validate=True)
@@ -599,7 +600,7 @@
 		if self.doctype in ["Sales Order", "Quotation"]:
 			for item in self.items:
 				item.gross_profit = flt(
-					((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item)
+					((item.base_rate - flt(item.valuation_rate)) * item.stock_qty), self.precision("amount", item)
 				)
 
 	def set_customer_address(self):
@@ -703,6 +704,9 @@
 def get_serial_and_batch_bundle(child, parent):
 	from erpnext.stock.serial_batch_bundle import SerialBatchCreation
 
+	if child.get("use_serial_batch_fields"):
+		return
+
 	if not frappe.db.get_single_value(
 		"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward"
 	):
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 11e9f9f..a67fbdc 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -7,7 +7,7 @@
 
 import frappe
 from frappe import _, bold
-from frappe.utils import cint, flt, get_link_to_form, getdate
+from frappe.utils import cint, cstr, flt, get_link_to_form, getdate
 
 import erpnext
 from erpnext.accounts.general_ledger import (
@@ -21,6 +21,9 @@
 from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
 	get_evaluated_inventory_dimension,
 )
+from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
+	get_type_of_transaction,
+)
 from erpnext.stock.stock_ledger import get_items_to_be_repost
 
 
@@ -43,6 +46,9 @@
 class StockController(AccountsController):
 	def validate(self):
 		super(StockController, self).validate()
+
+		if self.docstatus == 0:
+			self.validate_duplicate_serial_and_batch_bundle()
 		if not self.get("is_return"):
 			self.validate_inspection()
 		self.validate_serialized_batch()
@@ -52,6 +58,32 @@
 		self.validate_internal_transfer()
 		self.validate_putaway_capacity()
 
+	def validate_duplicate_serial_and_batch_bundle(self):
+		if sbb_list := [
+			item.get("serial_and_batch_bundle")
+			for item in self.items
+			if item.get("serial_and_batch_bundle")
+		]:
+			SLE = frappe.qb.DocType("Stock Ledger Entry")
+			data = (
+				frappe.qb.from_(SLE)
+				.select(SLE.voucher_type, SLE.voucher_no, SLE.serial_and_batch_bundle)
+				.where(
+					(SLE.docstatus == 1)
+					& (SLE.serial_and_batch_bundle.notnull())
+					& (SLE.serial_and_batch_bundle.isin(sbb_list))
+				)
+				.limit(1)
+			).run(as_dict=True)
+
+			if data:
+				data = data[0]
+				frappe.throw(
+					_("Serial and Batch Bundle {0} is already used in {1} {2}.").format(
+						frappe.bold(data.serial_and_batch_bundle), data.voucher_type, data.voucher_no
+					)
+				)
+
 	def make_gl_entries(self, gl_entries=None, from_repost=False):
 		if self.docstatus == 2:
 			make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
@@ -126,6 +158,131 @@
 				# remove extra whitespace and store one serial no on each line
 				row.serial_no = clean_serial_no_string(row.serial_no)
 
+	def make_bundle_using_old_serial_batch_fields(self, table_name=None):
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+		from erpnext.stock.serial_batch_bundle import SerialBatchCreation
+
+		if self.get("_action") == "update_after_submit":
+			return
+
+		# To handle test cases
+		if frappe.flags.in_test and frappe.flags.use_serial_and_batch_fields:
+			return
+
+		if not table_name:
+			table_name = "items"
+
+		if self.doctype == "Asset Capitalization":
+			table_name = "stock_items"
+
+		for row in self.get(table_name):
+			if row.serial_and_batch_bundle and (row.serial_no or row.batch_no):
+				self.validate_serial_nos_and_batches_with_bundle(row)
+
+			if not row.serial_no and not row.batch_no and not row.get("rejected_serial_no"):
+				continue
+
+			if not row.use_serial_batch_fields and (
+				row.serial_no or row.batch_no or row.get("rejected_serial_no")
+			):
+				row.use_serial_batch_fields = 1
+
+			if row.use_serial_batch_fields and (
+				not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
+			):
+				if self.doctype == "Stock Reconciliation":
+					qty = row.qty
+					type_of_transaction = "Inward"
+					warehouse = row.warehouse
+				elif table_name == "packed_items":
+					qty = row.qty
+					warehouse = row.warehouse
+					type_of_transaction = "Outward"
+					if self.is_return:
+						type_of_transaction = "Inward"
+				else:
+					qty = row.stock_qty if self.doctype != "Stock Entry" else row.transfer_qty
+					type_of_transaction = get_type_of_transaction(self, row)
+					warehouse = (
+						row.warehouse if self.doctype != "Stock Entry" else row.s_warehouse or row.t_warehouse
+					)
+
+				sn_doc = SerialBatchCreation(
+					{
+						"item_code": row.item_code,
+						"warehouse": warehouse,
+						"posting_date": self.posting_date,
+						"posting_time": self.posting_time,
+						"voucher_type": self.doctype,
+						"voucher_no": self.name,
+						"voucher_detail_no": row.name,
+						"qty": qty,
+						"type_of_transaction": type_of_transaction,
+						"company": self.company,
+						"is_rejected": 1 if row.get("rejected_warehouse") else 0,
+						"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
+						"batches": frappe._dict({row.batch_no: qty}) if row.batch_no else None,
+						"batch_no": row.batch_no,
+						"use_serial_batch_fields": row.use_serial_batch_fields,
+						"do_not_submit": True,
+					}
+				).make_serial_and_batch_bundle()
+
+				if sn_doc.is_rejected:
+					row.rejected_serial_and_batch_bundle = sn_doc.name
+					row.db_set(
+						{
+							"rejected_serial_and_batch_bundle": sn_doc.name,
+						}
+					)
+				else:
+					row.serial_and_batch_bundle = sn_doc.name
+					row.db_set(
+						{
+							"serial_and_batch_bundle": sn_doc.name,
+						}
+					)
+
+	def validate_serial_nos_and_batches_with_bundle(self, row):
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+		throw_error = False
+		if row.serial_no:
+			serial_nos = frappe.get_all(
+				"Serial and Batch Entry", fields=["serial_no"], filters={"parent": row.serial_and_batch_bundle}
+			)
+			serial_nos = sorted([cstr(d.serial_no) for d in serial_nos])
+			parsed_serial_nos = get_serial_nos(row.serial_no)
+
+			if len(serial_nos) != len(parsed_serial_nos):
+				throw_error = True
+			elif serial_nos != parsed_serial_nos:
+				for serial_no in serial_nos:
+					if serial_no not in parsed_serial_nos:
+						throw_error = True
+						break
+
+		elif row.batch_no:
+			batches = frappe.get_all(
+				"Serial and Batch Entry", fields=["batch_no"], filters={"parent": row.serial_and_batch_bundle}
+			)
+			batches = sorted([d.batch_no for d in batches])
+
+			if batches != [row.batch_no]:
+				throw_error = True
+
+		if throw_error:
+			frappe.throw(
+				_(
+					"At row {0}: Serial and Batch Bundle {1} has already created. Please remove the values from the serial no or batch no fields."
+				).format(row.idx, row.serial_and_batch_bundle)
+			)
+
+	def set_use_serial_batch_fields(self):
+		if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
+			for row in self.items:
+				row.use_serial_batch_fields = 1
+
 	def get_gl_entries(
 		self, warehouse_account=None, default_expense_account=None, default_cost_center=None
 	):
@@ -860,6 +1017,9 @@
 			"Stock Reconciliation",
 		)
 
+		if not frappe.get_all("Putaway Rule", limit=1):
+			return
+
 		if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
 			valid_doctype = False
 
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index 17a2b07..eac35b0 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -539,6 +539,10 @@
 	def __add_supplied_item(self, item_row, bom_item, qty):
 		bom_item.conversion_factor = item_row.conversion_factor
 		rm_obj = self.append(self.raw_material_table, bom_item)
+		if rm_obj.get("qty"):
+			# Qty field not exists
+			rm_obj.qty = 0.0
+
 		rm_obj.reference_name = item_row.name
 
 		if self.doctype == self.subcontract_data.order_doctype:
diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py
index fad216d..d2a3574 100644
--- a/erpnext/controllers/tests/test_accounts_controller.py
+++ b/erpnext/controllers/tests/test_accounts_controller.py
@@ -1108,18 +1108,18 @@
 		cr_note.reload()
 		cr_note.cancel()
 
-		# Exchange Gain/Loss Journal should've been created.
+		# with the introduction of 'cancel_system_generated_credit_debit_notes' in accounts controller
+		# JE(Credit Note) will be cancelled once the parent is cancelled
 		exc_je_for_si = self.get_journals_for(si.doctype, si.name)
 		exc_je_for_cr = self.get_journals_for(cr_note.doctype, cr_note.name)
-		self.assertNotEqual(exc_je_for_si, [])
-		self.assertEqual(len(exc_je_for_si), 1)
+		self.assertEqual(exc_je_for_si, [])
+		self.assertEqual(len(exc_je_for_si), 0)
 		self.assertEqual(len(exc_je_for_cr), 0)
 
-		# The Credit Note JE is still active and is referencing the sales invoice
-		# So, outstanding stays the same
+		# No references, full outstanding
 		si.reload()
-		self.assertEqual(si.outstanding_amount, 1)
-		self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
+		self.assertEqual(si.outstanding_amount, 2)
+		self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
 
 	def test_40_cost_center_from_payment_entry(self):
 		"""
diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py
index 47762ac..95a7bcb 100644
--- a/erpnext/controllers/tests/test_subcontracting_controller.py
+++ b/erpnext/controllers/tests/test_subcontracting_controller.py
@@ -401,7 +401,7 @@
 			{
 				"main_item_code": "Subcontracted Item SA4",
 				"item_code": "Subcontracted SRM Item 3",
-				"qty": 1.0,
+				"qty": 3.0,
 				"rate": 100.0,
 				"stock_uom": "Nos",
 				"warehouse": "_Test Warehouse - _TC",
@@ -914,12 +914,6 @@
 		else child_row.get("consumed_qty")
 	)
 
-	if child_row.serial_no:
-		details.serial_no.extend(get_serial_nos(child_row.serial_no))
-
-	if child_row.batch_no:
-		details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")
-
 	if child_row.serial_and_batch_bundle:
 		doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
 		for row in doc.get("entries"):
@@ -928,6 +922,12 @@
 
 			if row.batch_no:
 				details.batch_no[row.batch_no] += row.qty * (-1 if doc.type_of_transaction == "Outward" else 1)
+	else:
+		if child_row.serial_no:
+			details.serial_no.extend(get_serial_nos(child_row.serial_no))
+
+		if child_row.batch_no:
+			details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")
 
 
 def make_stock_transfer_entry(**args):
diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
index dea3f2d..4f7436f 100644
--- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
+++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
@@ -41,7 +41,9 @@
 			month_list = self.get_month_list()
 
 			for month in month_list:
-				self.columns.append({"fieldname": month, "fieldtype": based_on, "label": month, "width": 200})
+				self.columns.append(
+					{"fieldname": month, "fieldtype": based_on, "label": _(month), "width": 200}
+				)
 
 		elif self.filters.get("range") == "Quarterly":
 			for quarter in range(1, 5):
@@ -156,7 +158,7 @@
 
 		for column in self.columns:
 			if column["fieldname"] != "opportunity_owner" and column["fieldname"] != "sales_stage":
-				labels.append(column["fieldname"])
+				labels.append(_(column["fieldname"]))
 
 		self.chart = {"data": {"labels": labels, "datasets": datasets}, "type": "line"}
 
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 14b7656..308e6ca 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -42,7 +42,6 @@
 
 before_install = [
 	"erpnext.setup.install.check_setup_wizard_not_completed",
-	"erpnext.setup.install.check_frappe_version",
 ]
 after_install = "erpnext.setup.install.after_install"
 
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index 6a72c4f..e44d484 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -41,11 +41,49 @@
 	def validate(self):
 		self.validate_dates()
 		self.validate_duplicate_items()
+		self.set_party_item_code()
 
 	def validate_dates(self):
 		if getdate(self.from_date) > getdate(self.to_date):
 			frappe.throw(_("From date cannot be greater than To date"))
 
+	def set_party_item_code(self):
+		item_ref = {}
+		if self.blanket_order_type == "Selling":
+			item_ref = self.get_customer_items_ref()
+		else:
+			item_ref = self.get_supplier_items_ref()
+
+		if not item_ref:
+			return
+
+		for row in self.items:
+			row.party_item_code = item_ref.get(row.item_code)
+
+	def get_customer_items_ref(self):
+		items = [d.item_code for d in self.items]
+
+		return frappe._dict(
+			frappe.get_all(
+				"Item Customer Detail",
+				filters={"parent": ("in", items), "customer_name": self.customer},
+				fields=["parent", "ref_code"],
+				as_list=True,
+			)
+		)
+
+	def get_supplier_items_ref(self):
+		items = [d.item_code for d in self.items]
+
+		return frappe._dict(
+			frappe.get_all(
+				"Item Supplier",
+				filters={"parent": ("in", items), "supplier": self.supplier},
+				fields=["parent", "supplier_part_no"],
+				as_list=True,
+			)
+		)
+
 	def validate_duplicate_items(self):
 		item_list = []
 		for item in self.items:
@@ -90,6 +128,7 @@
 	def update_item(source, target, source_parent):
 		target_qty = source.get("qty") - source.get("ordered_qty")
 		target.qty = target_qty if flt(target_qty) >= 0 else 0
+		target.rate = source.get("rate")
 		item = get_item_defaults(target.item_code, source_parent.company)
 		if item:
 			target.item_name = item.get("item_name")
@@ -111,6 +150,10 @@
 			},
 		},
 	)
+
+	if target_doc.doctype == "Purchase Order":
+		target_doc.set_missing_values()
+
 	return target_doc
 
 
diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
index e9fc25b..3f3b6f0 100644
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
@@ -5,6 +5,7 @@
 from frappe.utils import add_months, today
 
 from erpnext import get_company_currency
+from erpnext.stock.doctype.item.test_item import make_item
 
 from .blanket_order import make_order
 
@@ -90,6 +91,30 @@
 		frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
 		po.submit()
 
+	def test_party_item_code(self):
+		item_doc = make_item("_Test Item 1 for Blanket Order")
+		item_code = item_doc.name
+
+		customer = "_Test Customer"
+		supplier = "_Test Supplier"
+
+		if not frappe.db.exists(
+			"Item Customer Detail", {"customer_name": customer, "parent": item_code}
+		):
+			item_doc.append("customer_items", {"customer_name": customer, "ref_code": "CUST-REF-1"})
+			item_doc.save()
+
+		if not frappe.db.exists("Item Supplier", {"supplier": supplier, "parent": item_code}):
+			item_doc.append("supplier_items", {"supplier": supplier, "supplier_part_no": "SUPP-PART-1"})
+			item_doc.save()
+
+		# Blanket Order for Selling
+		bo = make_blanket_order(blanket_order_type="Selling", customer=customer, item_code=item_code)
+		self.assertEqual(bo.items[0].party_item_code, "CUST-REF-1")
+
+		bo = make_blanket_order(blanket_order_type="Purchasing", supplier=supplier, item_code=item_code)
+		self.assertEqual(bo.items[0].party_item_code, "SUPP-PART-1")
+
 
 def make_blanket_order(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json
index 977ad54..aa7831f 100644
--- a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json
+++ b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2018-05-24 07:20:04.255236",
  "doctype": "DocType",
  "editable_grid": 1,
@@ -6,6 +7,7 @@
  "field_order": [
   "item_code",
   "item_name",
+  "party_item_code",
   "column_break_3",
   "qty",
   "rate",
@@ -62,10 +64,17 @@
    "fieldname": "terms_and_conditions",
    "fieldtype": "Text",
    "label": "Terms and Conditions"
+  },
+  {
+   "fieldname": "party_item_code",
+   "fieldtype": "Data",
+   "label": "Party Item Code",
+   "read_only": 1
   }
  ],
  "istable": 1,
- "modified": "2019-11-18 19:37:46.245878",
+ "links": [],
+ "modified": "2024-02-14 18:25:26.479672",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Blanket Order Item",
@@ -74,5 +83,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py
index 068c2e9..316d294 100644
--- a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py
+++ b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py
@@ -20,6 +20,7 @@
 		parent: DF.Data
 		parentfield: DF.Data
 		parenttype: DF.Data
+		party_item_code: DF.Data | None
 		qty: DF.Float
 		rate: DF.Currency
 		terms_and_conditions: DF.Text | None
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 6f35206..27c8493 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -1071,8 +1071,7 @@
 			frappe.qb.from_(sle)
 			.select(sle.valuation_rate)
 			.where((sle.item_code == item_code) & (sle.valuation_rate > 0) & (sle.is_cancelled == 0))
-			.orderby(sle.posting_date, order=frappe.qb.desc)
-			.orderby(sle.posting_time, order=frappe.qb.desc)
+			.orderby(sle.posting_datetime, order=frappe.qb.desc)
 			.orderby(sle.creation, order=frappe.qb.desc)
 			.limit(1)
 		).run(as_dict=True)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 079350b..35aebb9 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -239,12 +239,12 @@
 		for row in self.sub_operations:
 			self.total_completed_qty += row.completed_qty
 
-	def get_overlap_for(self, args, check_next_available_slot=False):
+	def get_overlap_for(self, args):
 		time_logs = []
 
-		time_logs.extend(self.get_time_logs(args, "Job Card Time Log", check_next_available_slot))
+		time_logs.extend(self.get_time_logs(args, "Job Card Time Log"))
 
-		time_logs.extend(self.get_time_logs(args, "Job Card Scheduled Time", check_next_available_slot))
+		time_logs.extend(self.get_time_logs(args, "Job Card Scheduled Time"))
 
 		if not time_logs:
 			return {}
@@ -269,7 +269,7 @@
 				self.workstation = workstation_time.get("workstation")
 				return workstation_time
 
-		return time_logs[-1]
+		return time_logs[0]
 
 	def has_overlap(self, production_capacity, time_logs):
 		overlap = False
@@ -308,7 +308,7 @@
 			return True
 		return overlap
 
-	def get_time_logs(self, args, doctype, check_next_available_slot=False):
+	def get_time_logs(self, args, doctype):
 		jc = frappe.qb.DocType("Job Card")
 		jctl = frappe.qb.DocType(doctype)
 
@@ -318,9 +318,6 @@
 			((jctl.from_time >= args.from_time) & (jctl.to_time <= args.to_time)),
 		]
 
-		if check_next_available_slot:
-			time_conditions.append(((jctl.from_time >= args.from_time) & (jctl.to_time >= args.to_time)))
-
 		query = (
 			frappe.qb.from_(jctl)
 			.from_(jc)
@@ -395,18 +392,28 @@
 
 	def validate_overlap_for_workstation(self, args, row):
 		# get the last record based on the to time from the job card
-		data = self.get_overlap_for(args, check_next_available_slot=True)
+		data = self.get_overlap_for(args)
+
 		if not self.workstation:
 			workstations = get_workstations(self.workstation_type)
 			if workstations:
 				# Get the first workstation
 				self.workstation = workstations[0]
 
+		if not data:
+			row.planned_start_time = args.from_time
+			return
+
 		if data:
 			if data.get("planned_start_time"):
-				row.planned_start_time = get_datetime(data.planned_start_time)
+				args.planned_start_time = get_datetime(data.planned_start_time)
 			else:
-				row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations())
+				args.planned_start_time = get_datetime(data.to_time + get_mins_between_operations())
+
+			args.from_time = args.planned_start_time
+			args.to_time = add_to_date(args.planned_start_time, minutes=row.remaining_time_in_mins)
+
+			self.validate_overlap_for_workstation(args, row)
 
 	def check_workstation_time(self, row):
 		workstation_doc = frappe.get_cached_doc("Workstation", self.workstation)
@@ -748,7 +755,7 @@
 			fields=["total_time_in_mins", "hour_rate"],
 			filters={"is_corrective_job_card": 1, "docstatus": 1, "work_order": self.work_order},
 		):
-			wo.corrective_operation_cost += flt(row.total_time_in_mins) * flt(row.hour_rate)
+			wo.corrective_operation_cost += flt(row.total_time_in_mins / 60) * flt(row.hour_rate)
 
 		wo.calculate_operating_cost()
 		wo.flags.ignore_validate_update_after_submit = True
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
index d3ad51f..63e3fa3 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
@@ -7,6 +7,7 @@
  "field_order": [
   "raw_materials_consumption_section",
   "material_consumption",
+  "get_rm_cost_from_consumption_entry",
   "column_break_3",
   "backflush_raw_materials_based_on",
   "capacity_planning",
@@ -202,13 +203,20 @@
    "fieldname": "set_op_cost_and_scrape_from_sub_assemblies",
    "fieldtype": "Check",
    "label": "Set Operating Cost / Scrape Items From Sub-assemblies"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: doc.material_consumption",
+   "fieldname": "get_rm_cost_from_consumption_entry",
+   "fieldtype": "Check",
+   "label": "Get Raw Materials Cost from Consumption Entry"
   }
  ],
  "icon": "icon-wrench",
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2023-12-28 16:37:44.874096",
+ "modified": "2024-02-08 19:00:37.561244",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Manufacturing Settings",
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
index 463ba9f..9a50111 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
@@ -26,6 +26,7 @@
 		default_scrap_warehouse: DF.Link | None
 		default_wip_warehouse: DF.Link | None
 		disable_capacity_planning: DF.Check
+		get_rm_cost_from_consumption_entry: DF.Check
 		job_card_excess_transfer: DF.Check
 		make_serial_no_batch_from_work_order: DF.Check
 		material_consumption: DF.Check
diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
index d07bf0f..06c1b49 100644
--- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
+++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
@@ -38,7 +38,8 @@
    "in_list_view": 1,
    "label": "Item Code",
    "options": "Item",
-   "reqd": 1
+   "reqd": 1,
+   "search_index": 1
   },
   {
    "fieldname": "item_name",
@@ -53,7 +54,8 @@
    "in_standard_filter": 1,
    "label": "For Warehouse",
    "options": "Warehouse",
-   "reqd": 1
+   "reqd": 1,
+   "search_index": 1
   },
   {
    "columns": 1,
@@ -141,7 +143,8 @@
    "fieldname": "from_warehouse",
    "fieldtype": "Link",
    "label": "From Warehouse",
-   "options": "Warehouse"
+   "options": "Warehouse",
+   "search_index": 1
   },
   {
    "fetch_from": "item_code.safety_stock",
@@ -199,7 +202,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2023-09-12 12:09:08.358326",
+ "modified": "2024-02-11 16:21:11.977018",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Material Request Plan Item",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 257b60c..54c3893 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -298,7 +298,8 @@
    "no_copy": 1,
    "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nClosed\nCancelled\nMaterial Requested",
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "search_index": 1
   },
   {
    "fieldname": "amended_from",
@@ -436,7 +437,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-12-26 16:31:13.740777",
+ "modified": "2024-02-11 15:42:47.642481",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 5dc5c38..517b2b0 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -312,9 +312,10 @@
 				so_item.parent,
 				so_item.item_code,
 				so_item.warehouse,
-				(
-					(so_item.qty - so_item.work_order_qty - so_item.delivered_qty) * so_item.conversion_factor
-				).as_("pending_qty"),
+				so_item.qty,
+				so_item.work_order_qty,
+				so_item.delivered_qty,
+				so_item.conversion_factor,
 				so_item.description,
 				so_item.name,
 				so_item.bom_no,
@@ -337,6 +338,11 @@
 
 		items = items_query.run(as_dict=True)
 
+		for item in items:
+			item.pending_qty = (
+				flt(item.qty) - max(item.work_order_qty, item.delivered_qty, 0) * item.conversion_factor
+			)
+
 		pi = frappe.qb.DocType("Packed Item")
 
 		packed_items_query = (
@@ -646,7 +652,10 @@
 				"project": self.project,
 			}
 
-			key = (d.item_code, d.sales_order, d.warehouse)
+			key = (d.item_code, d.sales_order, d.sales_order_item, d.warehouse)
+			if self.combine_items:
+				key = (d.item_code, d.sales_order, d.warehouse)
+
 			if not d.sales_order:
 				key = (d.name, d.item_code, d.warehouse)
 
@@ -1334,10 +1343,10 @@
 	)
 
 	date_field_mapper = {
-		"from_date": self.from_date >= so.transaction_date,
-		"to_date": self.to_date <= so.transaction_date,
-		"from_delivery_date": self.from_delivery_date >= so_item.delivery_date,
-		"to_delivery_date": self.to_delivery_date <= so_item.delivery_date,
+		"from_date": so.transaction_date >= self.from_date,
+		"to_date": so.transaction_date <= self.to_date,
+		"from_delivery_date": so_item.delivery_date >= self.from_delivery_date,
+		"to_delivery_date": so_item.delivery_date <= self.to_delivery_date,
 	}
 
 	for field, value in date_field_mapper.items():
@@ -1498,19 +1507,17 @@
 				frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get("idx")))
 
 			if bom_no:
-				if (
-					data.get("include_exploded_items")
-					and doc.get("sub_assembly_items")
-					and doc.get("skip_available_sub_assembly_item")
-				):
-					item_details = get_raw_materials_of_sub_assembly_items(
-						item_details,
-						company,
-						bom_no,
-						include_non_stock_items,
-						sub_assembly_items,
-						planned_qty=planned_qty,
-					)
+				if data.get("include_exploded_items") and doc.get("skip_available_sub_assembly_item"):
+					item_details = {}
+					if doc.get("sub_assembly_items"):
+						item_details = get_raw_materials_of_sub_assembly_items(
+							item_details,
+							company,
+							bom_no,
+							include_non_stock_items,
+							sub_assembly_items,
+							planned_qty=planned_qty,
+						)
 
 				elif data.get("include_exploded_items") and include_subcontracted_items:
 					# fetch exploded items from BOM
@@ -1762,23 +1769,23 @@
 	return reserved_qty_for_production_plan - reserved_qty_for_production
 
 
+@frappe.request_cache
 def get_non_completed_production_plans():
 	table = frappe.qb.DocType("Production Plan")
 	child = frappe.qb.DocType("Production Plan Item")
 
-	query = (
+	return (
 		frappe.qb.from_(table)
 		.inner_join(child)
 		.on(table.name == child.parent)
 		.select(table.name)
+		.distinct()
 		.where(
 			(table.docstatus == 1)
 			& (table.status.notin(["Completed", "Closed"]))
 			& (child.planned_qty > child.ordered_qty)
 		)
-	).run(as_dict=True)
-
-	return list(set([d.name for d in query]))
+	).run(pluck="name")
 
 
 def get_raw_materials_of_sub_assembly_items(
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 1c748a8..53537f9 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1232,6 +1232,35 @@
 			if row.item_code == "SubAssembly2 For SUB Test":
 				self.assertEqual(row.quantity, 10)
 
+	def test_sub_assembly_and_their_raw_materials_exists(self):
+		from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+
+		bom_tree = {
+			"FG1 For SUB Test": {
+				"SAB1 For SUB Test": {"CP1 For SUB Test": {}},
+				"SAB2 For SUB Test": {},
+			}
+		}
+
+		parent_bom = create_nested_bom(bom_tree, prefix="")
+		for item in ["SAB1 For SUB Test", "SAB2 For SUB Test"]:
+			make_stock_entry(item_code=item, qty=10, rate=100, target="_Test Warehouse - _TC")
+
+		plan = create_production_plan(
+			item_code=parent_bom.item,
+			planned_qty=10,
+			ignore_existing_ordered_qty=1,
+			do_not_submit=1,
+			skip_available_sub_assembly_item=1,
+			warehouse="_Test Warehouse - _TC",
+		)
+
+		items = get_items_for_material_requests(
+			plan.as_dict(), warehouses=[{"warehouse": "_Test Warehouse - _TC"}]
+		)
+
+		self.assertFalse(items)
+
 	def test_transfer_and_purchase_mrp_for_purchase_uom(self):
 		from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
 		from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index f6e9a07..c72232a 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -1776,6 +1776,159 @@
 			"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 0
 		)
 
+	@change_settings(
+		"Manufacturing Settings", {"material_consumption": 1, "get_rm_cost_from_consumption_entry": 1}
+	)
+	def test_get_rm_cost_from_consumption_entry(self):
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import (
+			make_stock_entry as make_stock_entry_test_record,
+		)
+
+		rm = make_item(properties={"is_stock_item": 1}).name
+		fg = make_item(properties={"is_stock_item": 1}).name
+
+		make_stock_entry_test_record(
+			purpose="Material Receipt",
+			item_code=rm,
+			target="Stores - _TC",
+			qty=10,
+			basic_rate=100,
+		)
+		make_stock_entry_test_record(
+			purpose="Material Receipt",
+			item_code=rm,
+			target="Stores - _TC",
+			qty=10,
+			basic_rate=200,
+		)
+
+		bom = make_bom(item=fg, raw_materials=[rm], rate=150).name
+		wo = make_wo_order_test_record(
+			production_item=fg,
+			bom_no=bom,
+			qty=10,
+		)
+
+		mte = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10))
+		mte.items[0].s_warehouse = "Stores - _TC"
+		mte.insert().submit()
+
+		mce = frappe.get_doc(make_stock_entry(wo.name, "Material Consumption for Manufacture", 10))
+		mce.insert().submit()
+
+		me = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10))
+		me.insert().submit()
+
+		valuation_rate = sum([item.valuation_rate * item.transfer_qty for item in mce.items]) / 10
+		self.assertEqual(me.items[0].valuation_rate, valuation_rate)
+
+	def test_capcity_planning_for_workstation(self):
+		frappe.db.set_single_value(
+			"Manufacturing Settings",
+			{
+				"disable_capacity_planning": 0,
+				"capacity_planning_for_days": 1,
+				"mins_between_operations": 10,
+			},
+		)
+
+		properties = {"is_stock_item": 1, "valuation_rate": 100}
+		fg_item = make_item("Test FG Item For Capacity Planning", properties).name
+
+		rm_item = make_item("Test RM Item For Capacity Planning", properties).name
+
+		workstation = "Test Workstation For Capacity Planning"
+		if not frappe.db.exists("Workstation", workstation):
+			make_workstation(workstation=workstation, production_capacity=1)
+
+		operation = "Test Operation For Capacity Planning"
+		if not frappe.db.exists("Operation", operation):
+			make_operation(operation=operation, workstation=workstation)
+
+		bom_doc = make_bom(
+			item=fg_item,
+			source_warehouse="Stores - _TC",
+			raw_materials=[rm_item],
+			with_operations=1,
+			do_not_submit=True,
+		)
+
+		bom_doc.append(
+			"operations",
+			{"operation": operation, "time_in_mins": 1420, "hour_rate": 100, "workstation": workstation},
+		)
+		bom_doc.submit()
+
+		# 1st Work Order,
+		# Capacity to run parallel the operation 'Test Operation For Capacity Planning' is 2
+		wo_doc = make_wo_order_test_record(
+			production_item=fg_item, qty=1, planned_start_date="2024-02-25 00:00:00", do_not_submit=1
+		)
+
+		wo_doc.submit()
+		job_cards = frappe.get_all(
+			"Job Card",
+			filters={"work_order": wo_doc.name},
+		)
+
+		self.assertEqual(len(job_cards), 1)
+
+		# 2nd Work Order,
+		wo_doc = make_wo_order_test_record(
+			production_item=fg_item, qty=1, planned_start_date="2024-02-25 00:00:00", do_not_submit=1
+		)
+
+		wo_doc.submit()
+		job_cards = frappe.get_all(
+			"Job Card",
+			filters={"work_order": wo_doc.name},
+		)
+
+		self.assertEqual(len(job_cards), 1)
+
+		# 3rd Work Order, capacity is full
+		wo_doc = make_wo_order_test_record(
+			production_item=fg_item, qty=1, planned_start_date="2024-02-25 00:00:00", do_not_submit=1
+		)
+
+		self.assertRaises(CapacityError, wo_doc.submit)
+
+		frappe.db.set_single_value(
+			"Manufacturing Settings", {"disable_capacity_planning": 1, "mins_between_operations": 0}
+		)
+
+
+def make_operation(**kwargs):
+	kwargs = frappe._dict(kwargs)
+
+	operation_doc = frappe.get_doc(
+		{
+			"doctype": "Operation",
+			"name": kwargs.operation,
+			"workstation": kwargs.workstation,
+		}
+	)
+	operation_doc.insert()
+
+	return operation_doc
+
+
+def make_workstation(**kwargs):
+	kwargs = frappe._dict(kwargs)
+
+	workstation_doc = frappe.get_doc(
+		{
+			"doctype": "Workstation",
+			"workstation_name": kwargs.workstation,
+			"workstation_type": kwargs.workstation_type,
+			"production_capacity": kwargs.production_capacity or 0,
+			"hour_rate": kwargs.hour_rate or 100,
+		}
+	)
+	workstation_doc.insert()
+
+	return workstation_doc
+
 
 def prepare_boms_for_sub_assembly_test():
 	if not frappe.db.exists("BOM", {"item": "Test Final SF Item 1"}):
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 1996e19..63c74b6 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -447,7 +447,8 @@
    "no_copy": 1,
    "options": "Production Plan",
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "search_index": 1
   },
   {
    "fieldname": "production_plan_item",
@@ -592,7 +593,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-08-11 18:35:49.852069",
+ "modified": "2024-02-11 15:47:13.454422",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 39beb36..5e22707 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -242,8 +242,12 @@
 	def calculate_operating_cost(self):
 		self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0
 		for d in self.get("operations"):
-			d.planned_operating_cost = flt(d.hour_rate) * (flt(d.time_in_mins) / 60.0)
-			d.actual_operating_cost = flt(d.hour_rate) * (flt(d.actual_operation_time) / 60.0)
+			d.planned_operating_cost = flt(
+				flt(d.hour_rate) * (flt(d.time_in_mins) / 60.0), d.precision("planned_operating_cost")
+			)
+			d.actual_operating_cost = flt(
+				flt(d.hour_rate) * (flt(d.actual_operation_time) / 60.0), d.precision("actual_operating_cost")
+			)
 
 			self.planned_operating_cost += flt(d.planned_operating_cost)
 			self.actual_operating_cost += flt(d.actual_operating_cost)
@@ -588,7 +592,6 @@
 	def prepare_data_for_job_card(self, row, index, plan_days, enable_capacity_planning):
 		self.set_operation_start_end_time(index, row)
 
-		original_start_time = row.planned_start_time
 		job_card_doc = create_job_card(
 			self, row, auto_create=True, enable_capacity_planning=enable_capacity_planning
 		)
@@ -597,11 +600,15 @@
 			row.planned_start_time = job_card_doc.scheduled_time_logs[-1].from_time
 			row.planned_end_time = job_card_doc.scheduled_time_logs[-1].to_time
 
-			if date_diff(row.planned_start_time, original_start_time) > plan_days:
+			if date_diff(row.planned_end_time, self.planned_start_date) > plan_days:
 				frappe.message_log.pop()
 				frappe.throw(
-					_("Unable to find the time slot in the next {0} days for the operation {1}.").format(
-						plan_days, row.operation
+					_(
+						"Unable to find the time slot in the next {0} days for the operation {1}. Please increase the 'Capacity Planning For (Days)' in the {2}."
+					).format(
+						plan_days,
+						row.operation,
+						get_link_to_form("Manufacturing Settings", "Manufacturing Settings"),
 					),
 					CapacityError,
 				)
diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
index f354d45..0f4d693 100644
--- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
+++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
@@ -36,7 +36,8 @@
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Item Code",
-   "options": "Item"
+   "options": "Item",
+   "search_index": 1
   },
   {
    "fieldname": "source_warehouse",
@@ -141,7 +142,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2022-09-28 10:50:43.512562",
+ "modified": "2024-02-11 15:45:32.318374",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Work Order Item",
diff --git a/erpnext/manufacturing/report/completed_work_orders/completed_work_orders.json b/erpnext/manufacturing/report/completed_work_orders/completed_work_orders.json
index be50e93..7925b8a 100644
--- a/erpnext/manufacturing/report/completed_work_orders/completed_work_orders.json
+++ b/erpnext/manufacturing/report/completed_work_orders/completed_work_orders.json
@@ -1,25 +1,28 @@
 {
- "add_total_row": 0, 
- "apply_user_permissions": 1, 
- "creation": "2013-08-12 12:44:27", 
- "disabled": 0, 
- "docstatus": 0, 
- "doctype": "Report", 
- "idx": 3, 
- "is_standard": "Yes", 
- "modified": "2018-02-13 04:58:51.549413", 
- "modified_by": "Administrator", 
- "module": "Manufacturing", 
- "name": "Completed Work Orders", 
- "owner": "Administrator", 
- "query": "SELECT\n  `tabWork Order`.name as \"Work Order:Link/Work Order:200\",\n  `tabWork Order`.creation as \"Date:Date:120\",\n  `tabWork Order`.production_item as \"Item:Link/Item:150\",\n  `tabWork Order`.qty as \"To Produce:Int:100\",\n  `tabWork Order`.produced_qty as \"Produced:Int:100\",\n  `tabWork Order`.company as \"Company:Link/Company:\"\nFROM\n  `tabWork Order`\nWHERE\n  `tabWork Order`.docstatus=1\n  AND ifnull(`tabWork Order`.produced_qty,0) = `tabWork Order`.qty", 
- "ref_doctype": "Work Order", 
- "report_name": "Completed Work Orders", 
- "report_type": "Query Report", 
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2013-08-12 12:44:27",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 3,
+ "is_standard": "Yes",
+ "letterhead": null,
+ "modified": "2024-02-21 14:35:14.301848",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Completed Work Orders",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "SELECT\n  `tabWork Order`.name as \"Work Order:Link/Work Order:200\",\n  `tabWork Order`.creation as \"Date:Date:120\",\n  `tabWork Order`.production_item as \"Item:Link/Item:150\",\n  `tabWork Order`.qty as \"To Produce:Int:100\",\n  `tabWork Order`.produced_qty as \"Produced:Int:100\",\n  `tabWork Order`.company as \"Company:Link/Company:\"\nFROM\n  `tabWork Order`\nWHERE\n  `tabWork Order`.docstatus=1\n  AND ifnull(`tabWork Order`.produced_qty,0) >= `tabWork Order`.qty",
+ "ref_doctype": "Work Order",
+ "report_name": "Completed Work Orders",
+ "report_type": "Query Report",
  "roles": [
   {
    "role": "Manufacturing User"
-  }, 
+  },
   {
    "role": "Stock User"
   }
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
index 97f30ef..8d37708 100644
--- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
@@ -58,7 +58,7 @@
 		query_filters["creation"] = ("between", [filters.get("from_date"), filters.get("to_date")])
 
 	data = frappe.get_all(
-		"Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc", debug=1
+		"Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc"
 	)
 
 	res = []
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 4ead7e7..5d30323 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -263,6 +263,7 @@
 
 [post_model_sync]
 execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
+erpnext.patches.v14_0.update_posting_datetime_and_dropped_indexes #22-02-2024
 erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
 erpnext.patches.v14_0.delete_shopify_doctypes
 erpnext.patches.v14_0.delete_healthcare_doctypes
@@ -353,7 +354,9 @@
 execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format"))
 erpnext.patches.v14_0.update_total_asset_cost_field
 erpnext.patches.v15_0.create_advance_payment_status
+erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
 # below migration patch should always run last
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
 erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
 erpnext.patches.v14_0.set_maintain_stock_for_bom_item
+erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_in_reconciliation_tool.py b/erpnext/patches/v14_0/create_accounting_dimensions_in_reconciliation_tool.py
new file mode 100644
index 0000000..4466eaa
--- /dev/null
+++ b/erpnext/patches/v14_0/create_accounting_dimensions_in_reconciliation_tool.py
@@ -0,0 +1,8 @@
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
+	create_accounting_dimensions_for_doctype,
+)
+
+
+def execute():
+	create_accounting_dimensions_for_doctype(doctype="Payment Reconciliation")
+	create_accounting_dimensions_for_doctype(doctype="Payment Reconciliation Allocation")
diff --git a/erpnext/patches/v14_0/update_posting_datetime_and_dropped_indexes.py b/erpnext/patches/v14_0/update_posting_datetime_and_dropped_indexes.py
new file mode 100644
index 0000000..ca126a4
--- /dev/null
+++ b/erpnext/patches/v14_0/update_posting_datetime_and_dropped_indexes.py
@@ -0,0 +1,19 @@
+import frappe
+
+
+def execute():
+	frappe.db.sql(
+		"""
+		UPDATE `tabStock Ledger Entry`
+			SET posting_datetime = DATE_FORMAT(timestamp(posting_date, posting_time), '%Y-%m-%d %H:%i:%s')
+	"""
+	)
+
+	drop_indexes()
+
+
+def drop_indexes():
+	if not frappe.db.has_index("tabStock Ledger Entry", "posting_sort_index"):
+		return
+
+	frappe.db.sql_ddl("ALTER TABLE `tabStock Ledger Entry` DROP INDEX `posting_sort_index`")
diff --git a/erpnext/patches/v15_0/delete_orphaned_asset_movement_item_records.py b/erpnext/patches/v15_0/delete_orphaned_asset_movement_item_records.py
new file mode 100644
index 0000000..a1d7dc9
--- /dev/null
+++ b/erpnext/patches/v15_0/delete_orphaned_asset_movement_item_records.py
@@ -0,0 +1,11 @@
+import frappe
+
+
+def execute():
+	# nosemgrep
+	frappe.db.sql(
+		"""
+		DELETE FROM `tabAsset Movement Item`
+		WHERE parent NOT IN (SELECT name FROM `tabAsset Movement`)
+		"""
+	)
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index b9d801c..e26d04a 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -83,7 +83,7 @@
 	def set_status(self):
 		self.status = {"0": "Draft", "1": "Submitted", "2": "Cancelled"}[str(self.docstatus or 0)]
 
-		if self.per_billed == 100:
+		if flt(self.per_billed, self.precision("per_billed")) >= 100.0:
 			self.status = "Billed"
 
 		if self.sales_invoice:
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 77ecf75..1d0d47e 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -368,7 +368,8 @@
 
 											let update_values = {
 												"serial_and_batch_bundle": r.name,
-												"qty": qty
+												"use_serial_batch_fields": 0,
+												"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
 											}
 
 											if (r.warehouse) {
@@ -408,7 +409,8 @@
 
 											let update_values = {
 												"serial_and_batch_bundle": r.name,
-												"rejected_qty": qty
+												"use_serial_batch_fields": 0,
+												"rejected_qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
 											}
 
 											if (r.warehouse) {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 26795f7..775bdb4 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -7,6 +7,7 @@
 		super.setup();
 		let me = this;
 
+		this.set_fields_onload_for_line_item();
 		this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle'];
 
 		frappe.flags.hide_serial_batch_dialog = true;
@@ -118,6 +119,13 @@
 					item.from_warehouse = frm.doc.set_from_warehouse;
 				}
 
+				if (item.docstatus === 0
+					&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
+					&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1
+				) {
+					frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1);
+				}
+
 				erpnext.accounts.dimensions.copy_dimension_from_first_row(frm, cdt, cdn, 'items');
 			}
 		});
@@ -136,6 +144,12 @@
 			});
 		}
 
+		if(this.frm.fields_dict['items'].grid.get_field('batch_no')) {
+			this.frm.set_query('batch_no', 'items', function(doc, cdt, cdn) {
+				return me.set_query_for_batch(doc, cdt, cdn);
+			});
+		}
+
 		if(
 			this.frm.docstatus < 2
 			&& this.frm.fields_dict["payment_terms_template"]
@@ -151,7 +165,7 @@
 		}
 
 		if(this.frm.fields_dict["items"]) {
-			this["items_remove"] = this.calculate_net_weight;
+			this["items_remove"] = this.process_item_removal;
 		}
 
 		if(this.frm.fields_dict["recurring_print_format"]) {
@@ -222,7 +236,19 @@
 				};
 			});
 		}
+	}
 
+	set_fields_onload_for_line_item() {
+		if (this.frm.is_new() && this.frm.doc?.items) {
+			this.frm.doc.items.forEach(item => {
+				if (item.docstatus === 0
+					&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
+					&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1
+				) {
+					frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1);
+				}
+			})
+		}
 	}
 
 	toggle_enable_for_stock_uom(field) {
@@ -462,6 +488,11 @@
 			this.frm.doc.doctype === 'Delivery Note') {
 			show_batch_dialog = 1;
 		}
+
+		if (show_batch_dialog && item.use_serial_batch_fields === 1) {
+			show_batch_dialog = 0;
+		}
+
 		item.barcode = null;
 
 
@@ -706,10 +737,10 @@
 				item.serial_no = item.serial_no.replace(/,/g, '\n');
 				item.conversion_factor = item.conversion_factor || 1;
 				refresh_field("serial_no", item.name, item.parentfield);
-				if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
+				if (!doc.is_return) {
 					setTimeout(() => {
 						me.update_qty(cdt, cdn);
-					}, 10000);
+					}, 3000);
 				}
 			}
 		}
@@ -910,25 +941,35 @@
 	due_date() {
 		// due_date is to be changed, payment terms template and/or payment schedule must
 		// be removed as due_date is automatically changed based on payment terms
-		if (this.frm.doc.due_date && !this.frm.updating_party_details && !this.frm.doc.is_pos) {
-			if (this.frm.doc.payment_terms_template ||
-				(this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length)) {
-				var message1 = "";
-				var message2 = "";
-				var final_message = __("Please clear the") + " ";
-
-				if (this.frm.doc.payment_terms_template) {
-					message1 = __("selected Payment Terms Template");
-					final_message = final_message + message1;
-				}
-
-				if ((this.frm.doc.payment_schedule || []).length) {
-					message2 = __("Payment Schedule Table");
-					if (message1.length !== 0) message2 = " and " + message2;
-					final_message = final_message + message2;
-				}
-				frappe.msgprint(final_message);
+		if (
+			this.frm.doc.due_date &&
+			!this.frm.updating_party_details &&
+			!this.frm.doc.is_pos &&
+			(
+				this.frm.doc.payment_terms_template ||
+				this.frm.doc.payment_schedule?.length
+			)
+		) {
+			const to_clear = [];
+			if (this.frm.doc.payment_terms_template) {
+				to_clear.push("Payment Terms Template");
 			}
+
+			if (this.frm.doc.payment_schedule?.length) {
+				to_clear.push("Payment Schedule Table");
+			}
+
+			frappe.confirm(
+				__(
+					"Do you want to clear the selected {0}?",
+					[frappe.utils.comma_and(to_clear.map(dt => __(dt)))]
+				),
+				() => {
+					this.frm.set_value("payment_terms_template", "");
+					this.frm.clear_table("payment_schedule");
+					this.frm.refresh_field("payment_schedule");
+				}
+			);
 		}
 	}
 
@@ -1242,20 +1283,6 @@
 		}
 	}
 
-	sync_bundle_data() {
-		let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
-
-		if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
-			const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
-			barcode_scanner.sync_bundle_data();
-			barcode_scanner.remove_item_from_localstorage();
-		}
-	}
-
-	before_save(doc) {
-		this.sync_bundle_data();
-	}
-
 	service_start_date(frm, cdt, cdn) {
 		var child = locals[cdt][cdn];
 
@@ -1270,6 +1297,11 @@
 		}
 	}
 
+	process_item_removal() {
+		this.frm.trigger("calculate_taxes_and_totals");
+		this.frm.trigger("calculate_net_weight");
+	}
+
 	calculate_net_weight(){
 		/* Calculate Total Net Weight then further applied shipping rule to calculate shipping charges.*/
 		var me = this;
@@ -1491,31 +1523,33 @@
 	}
 
 	remove_pricing_rule_for_item(item) {
-		let me = this;
-		return this.frm.call({
-			method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.remove_pricing_rule_for_item",
-			args: {
-				pricing_rules: item.pricing_rules,
-				item_details: {
-					"doctype": item.doctype,
-					"name": item.name,
-					"item_code": item.item_code,
-					"pricing_rules": item.pricing_rules,
-					"parenttype": item.parenttype,
-					"parent": item.parent,
-					"price_list_rate": item.price_list_rate
+		if (item.pricing_rules){
+			let me = this;
+			return this.frm.call({
+				method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.remove_pricing_rule_for_item",
+				args: {
+					pricing_rules: item.pricing_rules,
+					item_details: {
+						"doctype": item.doctype,
+						"name": item.name,
+						"item_code": item.item_code,
+						"pricing_rules": item.pricing_rules,
+						"parenttype": item.parenttype,
+						"parent": item.parent,
+						"price_list_rate": item.price_list_rate
+					},
+					item_code: item.item_code,
+					rate: item.price_list_rate,
 				},
-				item_code: item.item_code,
-				rate: item.price_list_rate,
-			},
-			callback: function(r) {
-				if (!r.exc && r.message) {
-					me.remove_pricing_rule(r.message);
-					me.calculate_taxes_and_totals();
-					if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on");
+				callback: function(r) {
+					if (!r.exc && r.message) {
+						me.remove_pricing_rule(r.message);
+						me.calculate_taxes_and_totals();
+						if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on");
+					}
 				}
-			}
-		});
+			});
+		}
 	}
 
 	apply_pricing_rule(item, calculate_taxes_and_totals) {
@@ -1621,18 +1655,6 @@
 		return item_list;
 	}
 
-	items_delete() {
-		this.update_localstorage_scanned_data();
-	}
-
-	update_localstorage_scanned_data() {
-		let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
-		if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
-			const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
-			barcode_scanner.update_localstorage_scanned_data();
-		}
-	}
-
 	_set_values_for_item_list(children) {
 		const items_rule_dict = {};
 
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index de46271..aee761f 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -40,7 +40,10 @@
 
 	is_perpetual_inventory_enabled: function(company) {
 		if(company) {
-			return frappe.get_doc(":Company", company).enable_perpetual_inventory
+			let company_local = locals[":Company"] && locals[":Company"][company];
+			if(company_local) {
+				return cint(company_local.enable_perpetual_inventory);
+			}
 		}
 	},
 
@@ -676,6 +679,7 @@
 			fieldname: frm.doc.doctype == 'Sales Order' ? "delivery_date" : "schedule_date",
 			in_list_view: 1,
 			label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date"),
+			default: frm.doc.doctype == 'Sales Order' ? frm.doc.delivery_date : frm.doc.schedule_date,
 			reqd: 1
 		})
 		fields.splice(3, 0, {
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index aacab0f..4d1c0c1 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -1,12 +1,15 @@
 erpnext.utils.BarcodeScanner = class BarcodeScanner {
 	constructor(opts) {
 		this.frm = opts.frm;
+		// frappe.flags.trigger_from_barcode_scanner is used for custom scripts
 
 		// field from which to capture input of scanned data
 		this.scan_field_name = opts.scan_field_name || "scan_barcode";
 		this.scan_barcode_field = this.frm.fields_dict[this.scan_field_name];
 
 		this.barcode_field = opts.barcode_field || "barcode";
+		this.serial_no_field = opts.serial_no_field || "serial_no";
+		this.batch_no_field = opts.batch_no_field || "batch_no";
 		this.uom_field = opts.uom_field || "uom";
 		this.qty_field = opts.qty_field || "qty";
 		// field name on row which defines max quantity to be scanned e.g. picklist
@@ -105,53 +108,52 @@
 				this.frm.has_items = false;
 			}
 
-			if (serial_no) {
-				this.is_duplicate_serial_no(row, item_code, serial_no)
-					.then((is_duplicate) => {
-						if (!is_duplicate) {
-							this.run_serially_tasks(row, data, resolve);
-						} else {
-							this.clean_up();
-							reject();
-							return;
-						}
-					});
-			} else {
-				this.run_serially_tasks(row, data, resolve);
+			if (this.is_duplicate_serial_no(row, serial_no)) {
+				this.clean_up();
+				reject();
+				return;
 			}
 
-
+			frappe.run_serially([
+				() => this.set_selector_trigger_flag(data),
+				() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
+					this.show_scan_message(row.idx, row.item_code, qty);
+				}),
+				() => this.set_barcode_uom(row, uom),
+				() => this.set_serial_no(row, serial_no),
+				() => this.set_batch_no(row, batch_no),
+				() => this.set_barcode(row, barcode),
+				() => this.clean_up(),
+				() => this.revert_selector_flag(),
+				() => resolve(row)
+			]);
 		});
 	}
 
-	run_serially_tasks(row, data, resolve) {
-		const {item_code, barcode, batch_no, serial_no, uom} = data;
+	// batch and serial selector is reduandant when all info can be added by scan
+	// this flag on item row is used by transaction.js to avoid triggering selector
+	set_selector_trigger_flag(data) {
+		const {batch_no, serial_no, has_batch_no, has_serial_no} = data;
 
-		frappe.run_serially([
-			() => this.set_serial_and_batch(row, item_code, serial_no, batch_no),
-			() => this.set_barcode(row, barcode),
-			() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
-				this.show_scan_message(row.idx, row.item_code, qty);
-			}),
-			() => this.set_barcode_uom(row, uom),
-			() => this.clean_up(),
-			() => {
-				if (row.serial_and_batch_bundle && !this.frm.is_new()) {
-					this.frm.save();
-				}
+		const require_selecting_batch = has_batch_no && !batch_no;
+		const require_selecting_serial = has_serial_no && !serial_no;
 
-				frappe.flags.trigger_from_barcode_scanner = false;
-			},
-			() => resolve(row),
-		]);
+		if (!(require_selecting_batch || require_selecting_serial)) {
+			frappe.flags.hide_serial_batch_dialog = true;
+		}
+	}
+
+	revert_selector_flag() {
+		frappe.flags.hide_serial_batch_dialog = false;
+		frappe.flags.trigger_from_barcode_scanner = false;
 	}
 
 	set_item(row, item_code, barcode, batch_no, serial_no) {
 		return new Promise(resolve => {
 			const increment = async (value = 1) => {
-				const item_data = {item_code: item_code};
-				item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
+				const item_data = {item_code: item_code, use_serial_batch_fields: 1.0};
 				frappe.flags.trigger_from_barcode_scanner = true;
+				item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
 				await frappe.model.set_value(row.doctype, row.name, item_data);
 				return value;
 			};
@@ -160,6 +162,8 @@
 				frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
 					increment(value).then((value) => resolve(value));
 				});
+			} else if (this.frm.has_items) {
+				this.prepare_item_for_scan(row, item_code, barcode, batch_no, serial_no);
 			} else {
 				increment().then((value) => resolve(value));
 			}
@@ -182,8 +186,9 @@
 			frappe.model.set_value(row.doctype, row.name, item_data);
 
 			frappe.run_serially([
+				() => this.set_batch_no(row, this.dialog.get_value("batch_no")),
 				() => this.set_barcode(row, this.dialog.get_value("barcode")),
-				() => this.set_serial_and_batch(row, item_code, this.dialog.get_value("serial_no"), this.dialog.get_value("batch_no")),
+				() => this.set_serial_no(row, this.dialog.get_value("serial_no")),
 				() => this.add_child_for_remaining_qty(row),
 				() => this.clean_up()
 			]);
@@ -337,144 +342,32 @@
 		}
 	}
 
-	async set_serial_and_batch(row, item_code, serial_no, batch_no) {
-		if (this.frm.is_new() || !row.serial_and_batch_bundle) {
-			this.set_bundle_in_localstorage(row, item_code, serial_no, batch_no);
-		} else if(row.serial_and_batch_bundle) {
-			frappe.call({
-				method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.update_serial_or_batch",
-				args: {
-					bundle_id: row.serial_and_batch_bundle,
-					serial_no: serial_no,
-					batch_no: batch_no,
-				},
-			})
-		}
-	}
+	async set_serial_no(row, serial_no) {
+		if (serial_no && frappe.meta.has_field(row.doctype, this.serial_no_field)) {
+			const existing_serial_nos = row[this.serial_no_field];
+			let new_serial_nos = "";
 
-	get_key_for_localstorage() {
-		let parts = this.frm.doc.name.split("-");
-		return parts[parts.length - 1] + this.frm.doc.doctype;
-	}
-
-	update_localstorage_scanned_data() {
-		let docname = this.frm.doc.name
-		if (localStorage[docname]) {
-			let items = JSON.parse(localStorage[docname]);
-			let existing_items = this.frm.doc.items.map(d => d.item_code);
-			if (!existing_items.length) {
-				localStorage.removeItem(docname);
-				return;
+			if (!!existing_serial_nos) {
+				new_serial_nos = existing_serial_nos + "\n" + serial_no;
+			} else {
+				new_serial_nos = serial_no;
 			}
-
-			for (let item_code in items) {
-				if (!existing_items.includes(item_code)) {
-					delete items[item_code];
-				}
-			}
-
-			localStorage[docname] = JSON.stringify(items);
+			await frappe.model.set_value(row.doctype, row.name, this.serial_no_field, new_serial_nos);
 		}
 	}
 
-	async set_bundle_in_localstorage(row, item_code, serial_no, batch_no) {
-		let docname = this.frm.doc.name
-
-		let entries = JSON.parse(localStorage.getItem(docname));
-		if (!entries) {
-			entries = {};
-		}
-
-		let key = item_code;
-		if (!entries[key]) {
-			entries[key] = [];
-		}
-
-		let existing_row = [];
-		if (!serial_no && batch_no) {
-			existing_row = entries[key].filter((e) => e.batch_no === batch_no);
-			if (existing_row.length) {
-				existing_row[0].qty += 1;
-			}
-		} else if (serial_no) {
-			existing_row = entries[key].filter((e) => e.serial_no === serial_no);
-			if (existing_row.length) {
-				frappe.throw(__("Serial No {0} has already scanned.", [serial_no]));
-			}
-		}
-
-		if (!existing_row.length) {
-			entries[key].push({
-				"serial_no": serial_no,
-				"batch_no": batch_no,
-				"qty": 1
-			});
-		}
-
-		localStorage.setItem(docname, JSON.stringify(entries));
-
-		// Auto remove from localstorage after 1 hour
-		setTimeout(() => {
-			localStorage.removeItem(docname);
-		}, 3600000)
-	}
-
-	remove_item_from_localstorage() {
-		let docname = this.frm.doc.name;
-		if (localStorage[docname]) {
-			localStorage.removeItem(docname);
-		}
-	}
-
-	async sync_bundle_data() {
-		let docname = this.frm.doc.name;
-
-		if (localStorage[docname]) {
-			let entries = JSON.parse(localStorage[docname]);
-			if (entries) {
-				for (let entry in entries) {
-					let row = this.frm.doc.items.filter((item) => {
-						if (item.item_code === entry) {
-							return true;
-						}
-					})[0];
-
-					if (row) {
-						this.create_serial_and_batch_bundle(row, entries, entry)
-							.then(() => {
-								if (!entries) {
-									localStorage.removeItem(docname);
-								}
-							});
-					}
-				}
-			}
-		}
-	}
-
-	async create_serial_and_batch_bundle(row, entries, key) {
-		frappe.call({
-			method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers",
-			args: {
-				entries: entries[key],
-				child_row: row,
-				doc: this.frm.doc,
-				warehouse: row.warehouse,
-				do_not_save: 1
-			},
-			callback: function(r) {
-				row.serial_and_batch_bundle = r.message.name;
-				delete entries[key];
-			}
-		})
-	}
-
 	async set_barcode_uom(row, uom) {
 		if (uom && frappe.meta.has_field(row.doctype, this.uom_field)) {
 			await frappe.model.set_value(row.doctype, row.name, this.uom_field, uom);
 		}
 	}
 
+	async set_batch_no(row, batch_no) {
+		if (batch_no && frappe.meta.has_field(row.doctype, this.batch_no_field)) {
+			await frappe.model.set_value(row.doctype, row.name, this.batch_no_field, batch_no);
+		}
+	}
+
 	async set_barcode(row, barcode) {
 		if (barcode && frappe.meta.has_field(row.doctype, this.barcode_field)) {
 			await frappe.model.set_value(row.doctype, row.name, this.barcode_field, barcode);
@@ -490,58 +383,13 @@
 		}
 	}
 
-	async is_duplicate_serial_no(row, item_code, serial_no) {
-		let is_duplicate = false;
-		const promise = new Promise((resolve, reject) => {
-			if (this.frm.is_new() || !row.serial_and_batch_bundle) {
-				is_duplicate = this.check_duplicate_serial_no_in_localstorage(item_code, serial_no);
-				if (is_duplicate) {
-					this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
-				}
+	is_duplicate_serial_no(row, serial_no) {
+		const is_duplicate = row[this.serial_no_field]?.includes(serial_no);
 
-				resolve(is_duplicate);
-			} else if (row.serial_and_batch_bundle) {
-				this.check_duplicate_serial_no_in_db(row, serial_no, (r) => {
-					if (r.message) {
-						this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
-					}
-
-					is_duplicate = r.message;
-					resolve(is_duplicate);
-				})
-			}
-		});
-
-		return await promise;
-	}
-
-	check_duplicate_serial_no_in_db(row, serial_no, response) {
-		frappe.call({
-			method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_duplicate_serial_no",
-			args: {
-				serial_no: serial_no,
-				bundle_id: row.serial_and_batch_bundle
-			},
-			callback(r) {
-				response(r);
-			}
-		});
-	}
-
-	check_duplicate_serial_no_in_localstorage(item_code, serial_no) {
-		let docname = this.frm.doc.name
-		let entries = JSON.parse(localStorage.getItem(docname));
-
-		if (!entries) {
-			return false;
+		if (is_duplicate) {
+			this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
 		}
-
-		let existing_row = [];
-		if (entries[item_code]) {
-			existing_row = entries[item_code].filter((e) => e.serial_no === serial_no);
-		}
-
-		return existing_row.length;
+		return is_duplicate;
 	}
 
 	get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
@@ -587,4 +435,4 @@
 	show_alert(msg, indicator, duration=3) {
 		frappe.show_alert({message: msg, indicator: indicator}, duration);
 	}
-};
+};
\ No newline at end of file
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index b8ec77f..a957530 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -339,7 +339,8 @@
 
 											frappe.model.set_value(item.doctype, item.name, {
 												"serial_and_batch_bundle": r.name,
-												"qty": qty
+												"use_serial_batch_fields": 0,
+												"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
 											});
 										}
 									}
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 80ade70..fccaf88 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -71,6 +71,10 @@
 		let warehouse = this.item?.type_of_transaction === "Outward" ?
 			(this.item.warehouse || this.item.s_warehouse) : "";
 
+		if (this.frm.doc.doctype === 'Stock Entry') {
+			warehouse = this.item.s_warehouse || this.item.t_warehouse;
+		}
+
 		if (!warehouse && this.frm.doc.doctype === 'Stock Reconciliation') {
 			warehouse = this.get_warehouse();
 		}
@@ -367,19 +371,11 @@
 					label: __('Batch No'),
 					in_list_view: 1,
 					get_query: () => {
-						if (this.item.type_of_transaction !== "Outward") {
-							return {
-								filters: {
-									'item': this.item.item_code,
-								}
-							}
-						} else {
-							return {
-								query : "erpnext.controllers.queries.get_batch_no",
-								filters: {
-									'item_code': this.item.item_code,
-									'warehouse': this.get_warehouse()
-								}
+						return {
+							query : "erpnext.controllers.queries.get_batch_no",
+							filters: {
+								'item_code': this.item.item_code,
+								'warehouse': this.item.s_warehouse || this.item.t_warehouse,
 							}
 						}
 					},
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 2f6775f..3744922 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -230,6 +230,7 @@
 
 		if self.flags.is_new_doc:
 			self.link_lead_address_and_contact()
+			self.copy_communication()
 
 		self.update_customer_groups()
 
@@ -291,6 +292,17 @@
 					linked_doc.append("links", dict(link_doctype="Customer", link_name=self.name))
 					linked_doc.save(ignore_permissions=self.flags.ignore_permissions)
 
+	def copy_communication(self):
+		if not self.lead_name or not frappe.db.get_single_value(
+			"CRM Settings", "carry_forward_communication_and_comments"
+		):
+			return
+
+		from erpnext.crm.utils import copy_comments, link_communications
+
+		copy_comments("Lead", self.lead_name, self)
+		link_communications("Lead", self.lead_name, self)
+
 	def validate_name_with_customer_group(self):
 		if frappe.db.exists("Customer Group", self.name):
 			frappe.throw(
@@ -560,15 +572,14 @@
 
 
 @frappe.whitelist()
-def send_emails(args):
-	args = json.loads(args)
-	subject = _("Credit limit reached for customer {0}").format(args.get("customer"))
+def send_emails(customer, customer_outstanding, credit_limit, credit_controller_users_list):
+	if isinstance(credit_controller_users_list, str):
+		credit_controller_users_list = json.loads(credit_controller_users_list)
+	subject = _("Credit limit reached for customer {0}").format(customer)
 	message = _("Credit limit has been crossed for customer {0} ({1}/{2})").format(
-		args.get("customer"), args.get("customer_outstanding"), args.get("credit_limit")
+		customer, customer_outstanding, credit_limit
 	)
-	frappe.sendmail(
-		recipients=args.get("credit_controller_users_list"), subject=subject, message=message
-	)
+	frappe.sendmail(recipients=credit_controller_users_list, subject=subject, message=message)
 
 
 def get_customer_outstanding(
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 47153a8..a8ebccd 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -297,11 +297,35 @@
 		if credit_limit > outstanding_amt:
 			set_credit_limit("_Test Customer", "_Test Company", credit_limit)
 
-		# Makes Sales invoice from Sales Order
-		so.save(ignore_permissions=True)
-		si = make_sales_invoice(so.name)
-		si.save(ignore_permissions=True)
-		self.assertRaises(frappe.ValidationError, make_sales_order)
+	def test_customer_credit_limit_after_submit(self):
+		from erpnext.controllers.accounts_controller import update_child_qty_rate
+		from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+
+		outstanding_amt = self.get_customer_outstanding_amount()
+		credit_limit = get_credit_limit("_Test Customer", "_Test Company")
+
+		if outstanding_amt <= 0.0:
+			item_qty = int((abs(outstanding_amt) + 200) / 100)
+			make_sales_order(qty=item_qty)
+
+		if credit_limit <= 0.0:
+			set_credit_limit("_Test Customer", "_Test Company", outstanding_amt + 100)
+
+		so = make_sales_order(rate=100, qty=1)
+		# Update qty in submitted Sales Order to trigger Credit Limit validation
+		fields = ["name", "item_code", "delivery_date", "conversion_factor", "qty", "rate", "uom", "idx"]
+		modified_item = frappe._dict()
+		for x in fields:
+			modified_item[x] = so.items[0].get(x)
+		modified_item["docname"] = so.items[0].name
+		modified_item["qty"] = 2
+		self.assertRaises(
+			frappe.ValidationError,
+			update_child_qty_rate,
+			so.doctype,
+			frappe.json.dumps([modified_item]),
+			so.name,
+		)
 
 	def test_customer_credit_limit_on_change(self):
 		outstanding_amt = self.get_customer_outstanding_amount()
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 79f24d1..ac392e7 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -515,6 +515,9 @@
 	def on_update(self):
 		pass
 
+	def on_update_after_submit(self):
+		self.check_credit_limit()
+
 	def before_update_after_submit(self):
 		self.validate_po()
 		self.validate_drop_ship()
@@ -906,6 +909,7 @@
 		target.run_method("set_missing_values")
 		target.run_method("set_po_nos")
 		target.run_method("calculate_taxes_and_totals")
+		target.run_method("set_use_serial_batch_fields")
 
 		if source.company_address:
 			target.update({"company_address": source.company_address})
@@ -1026,6 +1030,7 @@
 		target.run_method("set_missing_values")
 		target.run_method("set_po_nos")
 		target.run_method("calculate_taxes_and_totals")
+		target.run_method("set_use_serial_batch_fields")
 
 		if source.company_address:
 			target.update({"company_address": source.company_address})
@@ -1608,7 +1613,11 @@
 		"Sales Order",
 		source_name,
 		{
-			"Sales Order": {"doctype": "Pick List", "validation": {"docstatus": ["=", 1]}},
+			"Sales Order": {
+				"doctype": "Pick List",
+				"field_map": {"set_warehouse": "parent_warehouse"},
+				"validation": {"docstatus": ["=", 1]},
+			},
 			"Sales Order Item": {
 				"doctype": "Pick List Item",
 				"field_map": {"parent": "sales_order", "name": "sales_order_item"},
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 5ae48ee..b5189b8 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -20,6 +20,7 @@
 from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
 from erpnext.selling.doctype.sales_order.sales_order import (
 	WarehouseRequired,
+	create_pick_list,
 	make_delivery_note,
 	make_material_request,
 	make_raw_material_request,
@@ -2023,6 +2024,83 @@
 			frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Not Requested"
 		)
 
+	def test_pick_list_without_rejected_materials(self):
+		serial_and_batch_item = make_item(
+			"_Test Serial and Batch Item for Rejected Materials",
+			properties={
+				"has_serial_no": 1,
+				"has_batch_no": 1,
+				"create_new_batch": 1,
+				"batch_number_series": "BAT-TSBIFRM-.#####",
+				"serial_no_series": "SN-TSBIFRM-.#####",
+			},
+		).name
+
+		serial_item = make_item(
+			"_Test Serial Item for Rejected Materials",
+			properties={
+				"has_serial_no": 1,
+				"serial_no_series": "SN-TSIFRM-.#####",
+			},
+		).name
+
+		batch_item = make_item(
+			"_Test Batch Item for Rejected Materials",
+			properties={
+				"has_batch_no": 1,
+				"create_new_batch": 1,
+				"batch_number_series": "BAT-TBIFRM-.#####",
+			},
+		).name
+
+		normal_item = make_item("_Test Normal Item for Rejected Materials").name
+
+		warehouse = "_Test Warehouse - _TC"
+		rejected_warehouse = "_Test Dummy Rejected Warehouse - _TC"
+
+		if not frappe.db.exists("Warehouse", rejected_warehouse):
+			frappe.get_doc(
+				{
+					"doctype": "Warehouse",
+					"warehouse_name": rejected_warehouse,
+					"company": "_Test Company",
+					"warehouse_group": "_Test Warehouse Group",
+					"is_rejected_warehouse": 1,
+				}
+			).insert()
+
+		se = make_stock_entry(item_code=normal_item, qty=1, to_warehouse=warehouse, do_not_submit=True)
+		for item in [serial_and_batch_item, serial_item, batch_item]:
+			se.append("items", {"item_code": item, "qty": 1, "t_warehouse": warehouse})
+
+		se.save()
+		se.submit()
+
+		se = make_stock_entry(
+			item_code=normal_item, qty=1, to_warehouse=rejected_warehouse, do_not_submit=True
+		)
+		for item in [serial_and_batch_item, serial_item, batch_item]:
+			se.append("items", {"item_code": item, "qty": 1, "t_warehouse": rejected_warehouse})
+
+		se.save()
+		se.submit()
+
+		so = make_sales_order(item_code=normal_item, qty=2, do_not_submit=True)
+
+		for item in [serial_and_batch_item, serial_item, batch_item]:
+			so.append("items", {"item_code": item, "qty": 2, "warehouse": warehouse})
+
+		so.save()
+		so.submit()
+
+		pick_list = create_pick_list(so.name)
+
+		pick_list.save()
+		for row in pick_list.locations:
+			self.assertEqual(row.qty, 1.0)
+			self.assertFalse(row.warehouse == rejected_warehouse)
+			self.assertTrue(row.warehouse == warehouse)
+
 
 def automatically_fetch_payment_terms(enable=1):
 	accounts_settings = frappe.get_doc("Accounts Settings")
diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
index 7d28f2b..f2f1e4c 100644
--- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
+++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
@@ -206,42 +206,36 @@
 
 def get_actual_data(filters, sales_users_or_territory_data, date_field, sales_field):
 	fiscal_year = get_fiscal_year(fiscal_year=filters.get("fiscal_year"), as_dict=1)
-	dates = [fiscal_year.year_start_date, fiscal_year.year_end_date]
 
-	select_field = "`tab{0}`.{1}".format(filters.get("doctype"), sales_field)
-	child_table = "`tab{0}`".format(filters.get("doctype") + " Item")
+	parent_doc = frappe.qb.DocType(filters.get("doctype"))
+	child_doc = frappe.qb.DocType(filters.get("doctype") + " Item")
+	sales_team = frappe.qb.DocType("Sales Team")
+
+	query = (
+		frappe.qb.from_(parent_doc)
+		.inner_join(child_doc)
+		.on(child_doc.parent == parent_doc.name)
+		.inner_join(sales_team)
+		.on(sales_team.parent == parent_doc.name)
+		.select(
+			child_doc.item_group,
+			(child_doc.stock_qty * sales_team.allocated_percentage / 100).as_("stock_qty"),
+			(child_doc.base_net_amount * sales_team.allocated_percentage / 100).as_("base_net_amount"),
+			sales_team.sales_person,
+			parent_doc[date_field],
+		)
+		.where(
+			(parent_doc.docstatus == 1)
+			& (parent_doc[date_field].between(fiscal_year.year_start_date, fiscal_year.year_end_date))
+		)
+	)
 
 	if sales_field == "sales_person":
-		select_field = "`tabSales Team`.sales_person"
-		child_table = "`tab{0}`, `tabSales Team`".format(filters.get("doctype") + " Item")
-		cond = """`tabSales Team`.parent = `tab{0}`.name and
-			`tabSales Team`.sales_person in ({1}) """.format(
-			filters.get("doctype"), ",".join(["%s"] * len(sales_users_or_territory_data))
-		)
+		query = query.where(sales_team.sales_person.isin(sales_users_or_territory_data))
 	else:
-		cond = "`tab{0}`.{1} in ({2})".format(
-			filters.get("doctype"), sales_field, ",".join(["%s"] * len(sales_users_or_territory_data))
-		)
+		query = query.where(parent_doc[sales_field].isin(sales_users_or_territory_data))
 
-	return frappe.db.sql(
-		""" SELECT `tab{child_doc}`.item_group,
-			`tab{child_doc}`.stock_qty, `tab{child_doc}`.base_net_amount,
-			{select_field}, `tab{parent_doc}`.{date_field}
-		FROM `tab{parent_doc}`, {child_table}
-		WHERE
-			`tab{child_doc}`.parent = `tab{parent_doc}`.name
-			and `tab{parent_doc}`.docstatus = 1 and {cond}
-			and `tab{parent_doc}`.{date_field} between %s and %s""".format(
-			cond=cond,
-			date_field=date_field,
-			select_field=select_field,
-			child_table=child_table,
-			parent_doc=filters.get("doctype"),
-			child_doc=filters.get("doctype") + " Item",
-		),
-		tuple(sales_users_or_territory_data + dates),
-		as_dict=1,
-	)
+	return query.run(as_dict=True)
 
 
 def get_parents_data(filters, partner_doctype):
diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py
new file mode 100644
index 0000000..4ae5d2b
--- /dev/null
+++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py
@@ -0,0 +1,84 @@
+import frappe
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import flt, nowdate
+
+from erpnext.accounts.utils import get_fiscal_year
+from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+from erpnext.selling.report.sales_person_target_variance_based_on_item_group.sales_person_target_variance_based_on_item_group import (
+	execute,
+)
+
+
+class TestSalesPersonTargetVarianceBasedOnItemGroup(FrappeTestCase):
+	def setUp(self):
+		self.fiscal_year = get_fiscal_year(nowdate())[0]
+
+	def tearDown(self):
+		frappe.db.rollback()
+
+	def test_achieved_target_and_variance(self):
+		# Create a Target Distribution
+		distribution = frappe.new_doc("Monthly Distribution")
+		distribution.distribution_id = "Target Report Distribution"
+		distribution.fiscal_year = self.fiscal_year
+		distribution.get_months()
+		distribution.insert()
+
+		# Create sales people with targets
+		person_1 = create_sales_person_with_target("Sales Person 1", self.fiscal_year, distribution.name)
+		person_2 = create_sales_person_with_target("Sales Person 2", self.fiscal_year, distribution.name)
+
+		# Create a Sales Order with 50-50 contribution
+		so = make_sales_order(
+			rate=1000,
+			qty=20,
+			do_not_submit=True,
+		)
+		so.set(
+			"sales_team",
+			[
+				{
+					"sales_person": person_1.name,
+					"allocated_percentage": 50,
+					"allocated_amount": 10000,
+				},
+				{
+					"sales_person": person_2.name,
+					"allocated_percentage": 50,
+					"allocated_amount": 10000,
+				},
+			],
+		)
+		so.submit()
+
+		# Check Achieved Target and Variance
+		result = execute(
+			frappe._dict(
+				{
+					"fiscal_year": self.fiscal_year,
+					"doctype": "Sales Order",
+					"period": "Yearly",
+					"target_on": "Quantity",
+				}
+			)
+		)[1]
+		row = frappe._dict(result[0])
+		self.assertSequenceEqual(
+			[flt(value, 2) for value in (row.total_target, row.total_achieved, row.total_variance)],
+			[50, 10, -40],
+		)
+
+
+def create_sales_person_with_target(sales_person_name, fiscal_year, distribution_id):
+	sales_person = frappe.new_doc("Sales Person")
+	sales_person.sales_person_name = sales_person_name
+	sales_person.append(
+		"targets",
+		{
+			"fiscal_year": fiscal_year,
+			"target_qty": 50,
+			"target_amount": 30000,
+			"distribution_id": distribution_id,
+		},
+	)
+	return sales_person.insert()
diff --git a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
index 9f3ba0d..847488f 100644
--- a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
+++ b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
@@ -36,6 +36,7 @@
 					d.base_net_amount,
 					d.sales_person,
 					d.allocated_percentage,
+					(d.stock_qty * d.allocated_percentage / 100),
 					d.contribution_amt,
 					company_currency,
 				]
@@ -103,7 +104,7 @@
 			"fieldtype": "Link",
 			"width": 140,
 		},
-		{"label": _("Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 140},
+		{"label": _("SO Total Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 140},
 		{
 			"label": _("Amount"),
 			"options": "currency",
@@ -120,6 +121,12 @@
 		},
 		{"label": _("Contribution %"), "fieldname": "contribution", "fieldtype": "Float", "width": 140},
 		{
+			"label": _("Contribution Qty"),
+			"fieldname": "contribution_qty",
+			"fieldtype": "Float",
+			"width": 140,
+		},
+		{
 			"label": _("Contribution Amount"),
 			"options": "currency",
 			"fieldname": "contribution_amt",
diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py
index df2c49b..688d45a 100644
--- a/erpnext/setup/demo.py
+++ b/erpnext/setup/demo.py
@@ -95,7 +95,17 @@
 
 def make_transactions(company):
 	frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1)
-	start_date = get_fiscal_year(date=getdate())[1]
+	from erpnext.accounts.utils import FiscalYearError
+
+	try:
+		start_date = get_fiscal_year(date=getdate())[1]
+	except FiscalYearError:
+		# User might have setup fiscal year for previous or upcoming years
+		active_fiscal_years = frappe.db.get_all("Fiscal Year", filters={"disabled": 0}, as_list=1)
+		if active_fiscal_years:
+			start_date = frappe.db.get_value("Fiscal Year", active_fiscal_years[0][0], "year_start_date")
+		else:
+			frappe.throw(_("There are no active Fiscal Years for which Demo Data can be generated."))
 
 	for doctype in frappe.get_hooks("demo_transaction_doctypes"):
 		data = read_data_file_using_hooks(doctype)
@@ -159,6 +169,7 @@
 
 			if i % 2 != 0:
 				payment = get_payment_entry(invoice.doctype, invoice.name)
+				payment.posting_date = order.transaction_date
 				payment.reference_no = invoice.name
 				payment.submit()
 
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 6239864..527f742 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -2,14 +2,12 @@
 # License: GNU General Public License v3. See license.txt
 
 
-import click
 import frappe
 from frappe import _
 from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
 from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
 from frappe.utils import cint
 
-import erpnext
 from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules
 from erpnext.setup.doctype.incoterm.incoterm import create_incoterms
 
@@ -42,25 +40,6 @@
 		frappe.throw(message)  # nosemgrep
 
 
-def check_frappe_version():
-	def major_version(v: str) -> str:
-		return v.split(".")[0]
-
-	frappe_version = major_version(frappe.__version__)
-	erpnext_version = major_version(erpnext.__version__)
-
-	if frappe_version == erpnext_version:
-		return
-
-	click.secho(
-		f"You're attempting to install ERPNext version {erpnext_version} with Frappe version {frappe_version}. "
-		"This is not supported and will result in broken install. Switch to correct branch before installing.",
-		fg="red",
-	)
-
-	raise SystemExit(1)
-
-
 def set_single_defaults():
 	for dt in (
 		"Accounts Settings",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 58990d4..a3903a3 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -398,6 +398,13 @@
 			self.check_credit_limit()
 		elif self.issue_credit_note:
 			self.make_return_invoice()
+
+		for table_name in ["items", "packed_items"]:
+			if not self.get(table_name):
+				continue
+
+			self.make_bundle_using_old_serial_batch_fields(table_name)
+
 		# Updating stock ledger should always be called after updating prevdoc status,
 		# because updating reserved qty in bin depends upon updated delivered qty in SO
 		self.update_stock_ledger()
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 0f12f38..293ef9f 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -200,7 +200,6 @@
 			},
 		)
 
-		frappe.flags.ignore_serial_batch_bundle_validation = True
 		serial_nos = [
 			"OSN-1",
 			"OSN-2",
@@ -239,6 +238,8 @@
 		)
 
 		se_doc.items[0].serial_no = "\n".join(serial_nos)
+
+		frappe.flags.use_serial_and_batch_fields = True
 		se_doc.submit()
 
 		self.assertEqual(sorted(get_serial_nos(se_doc.items[0].serial_no)), sorted(serial_nos))
@@ -294,6 +295,8 @@
 			self.assertTrue(serial_no in serial_nos)
 			self.assertFalse(serial_no in returned_serial_nos1)
 
+		frappe.flags.use_serial_and_batch_fields = False
+
 	def test_sales_return_for_non_bundled_items_partial(self):
 		company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
 
@@ -1075,6 +1078,8 @@
 		self.assertEqual(si2.items[1].qty, 1)
 
 	def test_delivery_note_bundle_with_batched_item(self):
+		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
+
 		batched_bundle = make_item("_Test Batched bundle", {"is_stock_item": 0})
 		batched_item = make_item(
 			"_Test Batched Item",
@@ -1096,6 +1101,8 @@
 		batch_no = get_batch_from_bundle(dn.packed_items[0].serial_and_batch_bundle)
 		self.assertTrue(batch_no)
 
+		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
+
 	def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
 		from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
 			create_payment_terms_template,
@@ -1548,6 +1555,53 @@
 		self.assertEqual(so.items[0].rate, rate)
 		self.assertEqual(dn.items[0].rate, so.items[0].rate)
 
+	def test_use_serial_batch_fields_for_packed_items(self):
+		bundle_item = make_item("Test _Packed Product Bundle Item ", {"is_stock_item": 0})
+		serial_item = make_item(
+			"Test _Packed Serial Item ",
+			{"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SN-TESTSERIAL-.#####"},
+		)
+		batch_item = make_item(
+			"Test _Packed Batch Item ",
+			{
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"batch_no_series": "BATCH-TESTSERIAL-.#####",
+				"create_new_batch": 1,
+			},
+		)
+		make_product_bundle(parent=bundle_item.name, items=[serial_item.name, batch_item.name])
+
+		item_details = {}
+		for item in [serial_item, batch_item]:
+			se = make_stock_entry(
+				item_code=item.name, target="_Test Warehouse - _TC", qty=5, basic_rate=100
+			)
+			item_details[item.name] = se.items[0].serial_and_batch_bundle
+
+		dn = create_delivery_note(item_code=bundle_item.name, qty=1, do_not_submit=True)
+		serial_no = ""
+		for row in dn.packed_items:
+			row.use_serial_batch_fields = 1
+
+			if row.item_code == serial_item.name:
+				serial_and_batch_bundle = item_details[serial_item.name]
+				row.serial_no = get_serial_nos_from_bundle(serial_and_batch_bundle)[3]
+				serial_no = row.serial_no
+			else:
+				serial_and_batch_bundle = item_details[batch_item.name]
+				row.batch_no = get_batch_from_bundle(serial_and_batch_bundle)
+
+		dn.submit()
+		dn.load_from_db()
+
+		for row in dn.packed_items:
+			self.assertTrue(row.serial_no or row.batch_no)
+			self.assertTrue(row.serial_and_batch_bundle)
+
+			if row.serial_no:
+				self.assertEqual(row.serial_no, serial_no)
+
 
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
@@ -1563,7 +1617,7 @@
 	dn.return_against = args.return_against
 
 	bundle_id = None
-	if args.get("batch_no") or args.get("serial_no"):
+	if not args.use_serial_batch_fields and (args.get("batch_no") or args.get("serial_no")):
 		type_of_transaction = args.type_of_transaction or "Outward"
 
 		if dn.is_return:
@@ -1605,6 +1659,9 @@
 			"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
 			"cost_center": args.cost_center or "_Test Cost Center - _TC",
 			"target_warehouse": args.target_warehouse,
+			"use_serial_batch_fields": args.use_serial_batch_fields,
+			"serial_no": args.serial_no if args.use_serial_batch_fields else None,
+			"batch_no": args.batch_no if args.use_serial_batch_fields else None,
 		},
 	)
 
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 a44b9ac..247672f 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -80,8 +80,11 @@
   "section_break_40",
   "pick_serial_and_batch",
   "serial_and_batch_bundle",
+  "use_serial_batch_fields",
   "column_break_eaoe",
+  "section_break_qyjv",
   "serial_no",
+  "column_break_rxvc",
   "batch_no",
   "available_qty_section",
   "actual_batch_qty",
@@ -850,6 +853,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
@@ -859,6 +863,7 @@
    "search_index": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "pick_serial_and_batch",
    "fieldtype": "Button",
    "label": "Pick Serial / Batch No"
@@ -874,27 +879,40 @@
    "fieldtype": "Column Break"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "serial_no",
    "fieldtype": "Text",
-   "hidden": 1,
-   "label": "Serial No",
-   "read_only": 1
+   "label": "Serial No"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "batch_no",
    "fieldtype": "Link",
-   "hidden": 1,
    "label": "Batch No",
    "options": "Batch",
-   "read_only": 1,
    "search_index": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
+   "fieldname": "section_break_qyjv",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_rxvc",
+   "fieldtype": "Column Break"
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-14 18:37:38.638144",
+ "modified": "2024-02-04 14:10:31.750340",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.py b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.py
index c11c410..b76f742 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.py
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.py
@@ -82,6 +82,7 @@
 		target_warehouse: DF.Link | None
 		total_weight: DF.Float
 		uom: DF.Link
+		use_serial_batch_fields: DF.Check
 		warehouse: DF.Link | None
 		weight_per_unit: DF.Float
 		weight_uom: DF.Link | None
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index feb4583..949c109 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -1122,6 +1122,7 @@
 		frappe.throw(_("Item {0} is cancelled").format(item_code))
 
 
+@frappe.request_cache
 def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
 	"""returns last purchase details in stock uom"""
 	# get last purchase order item details
diff --git a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json
index 9b2b5da..736eb9d 100644
--- a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json
+++ b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json
@@ -1,187 +1,77 @@
 {
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2013-02-22 01:28:02", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Document", 
- "editable_grid": 1, 
+ "actions": [],
+ "creation": "2013-02-22 01:28:02",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "receipt_document_type",
+  "receipt_document",
+  "supplier",
+  "col_break1",
+  "posting_date",
+  "grand_total"
+ ],
  "fields": [
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "receipt_document_type", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Receipt Document Type", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "\nPurchase Invoice\nPurchase Receipt", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "receipt_document_type",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Receipt Document Type",
+   "options": "\nPurchase Invoice\nPurchase Receipt",
+   "reqd": 1
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "receipt_document", 
-   "fieldtype": "Dynamic Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Receipt Document", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "purchase_receipt_no", 
-   "oldfieldtype": "Link", 
-   "options": "receipt_document_type", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "220px", 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "fieldname": "receipt_document",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Receipt Document",
+   "oldfieldname": "purchase_receipt_no",
+   "oldfieldtype": "Link",
+   "options": "receipt_document_type",
+   "print_width": "220px",
+   "reqd": 1,
    "width": "220px"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "supplier", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Supplier", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Supplier", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "supplier",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Supplier",
+   "options": "Supplier",
+   "read_only": 1
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "col_break1", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0, 
+   "fieldname": "col_break1",
+   "fieldtype": "Column Break",
    "width": "50%"
-  }, 
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "posting_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Posting Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "posting_date",
+   "fieldtype": "Date",
+   "label": "Posting Date",
+   "read_only": 1
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "grand_total", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Grand Total", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
+   "fieldname": "grand_total",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Grand Total",
+   "options": "Company:company:default_currency",
+   "read_only": 1
   }
- ], 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
-
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2020-09-18 17:26:09.703215", 
- "modified_by": "Administrator", 
- "module": "Stock", 
- "name": "Landed Cost Purchase Receipt", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "sort_order": "ASC", 
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2024-02-26 18:41:06.281750",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Landed Cost Purchase Receipt",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index b4f7708..baff540 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -65,6 +65,7 @@
 	def validate(self):
 		self.check_mandatory()
 		self.validate_receipt_documents()
+		self.validate_line_items()
 		init_landed_taxes_and_totals(self)
 		self.set_total_taxes_and_charges()
 		if not self.get("items"):
@@ -72,6 +73,26 @@
 
 		self.set_applicable_charges_on_item()
 
+	def validate_line_items(self):
+		for d in self.get("items"):
+			if (
+				d.docstatus == 0
+				and d.purchase_receipt_item
+				and not frappe.db.exists(
+					d.receipt_document_type + " Item",
+					{"name": d.purchase_receipt_item, "parent": d.receipt_document},
+				)
+			):
+				frappe.throw(
+					_("Row {0}: {2} Item {1} does not exist in {2} {3}").format(
+						d.idx,
+						frappe.bold(d.purchase_receipt_item),
+						d.receipt_document_type,
+						frappe.bold(d.receipt_document),
+					),
+					title=_("Incorrect Reference Document (Purchase Receipt Item)"),
+				)
+
 	def check_mandatory(self):
 		if not self.get("purchase_receipts"):
 			frappe.throw(_("Please enter Receipt Document"))
@@ -149,6 +170,13 @@
 				self.get("items")[item_count - 1].applicable_charges += diff
 
 	def validate_applicable_charges_for_item(self):
+		if self.distribute_charges_based_on == "Distribute Manually" and len(self.taxes) > 1:
+			frappe.throw(
+				_(
+					"Please keep one Applicable Charges, when 'Distribute Charges Based On' is 'Distribute Manually'. For more charges, please create another Landed Cost Voucher."
+				)
+			)
+
 		based_on = self.distribute_charges_based_on.lower()
 
 		if based_on != "distribute manually":
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index e80218a..a913e28 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -199,6 +199,7 @@
 
 	get_item_data: function(frm, item, overwrite_warehouse=false) {
 		if (item && !item.item_code) { return; }
+
 		frappe.call({
 			method: "erpnext.stock.get_item_details.get_item_details",
 			args: {
@@ -225,12 +226,22 @@
 			},
 			callback: function(r) {
 				const d = item;
-				const qty_fields = ['actual_qty', 'projected_qty', 'min_order_qty'];
+				const allow_to_change_fields = ['actual_qty', 'projected_qty', 'min_order_qty', 'item_name', 'description', 'stock_uom', 'uom', 'conversion_factor', 'stock_qty'];
 
 				if(!r.exc) {
-					$.each(r.message, function(k, v) {
-						if(!d[k] || in_list(qty_fields, k)) d[k] = v;
+					$.each(r.message, function(key, value) {
+						if(!d[key] || allow_to_change_fields.includes(key)) {
+							d[key] = value;
+						}
 					});
+
+					if (d.price_list_rate != r.message.price_list_rate) {
+						d.rate = 0.0;
+						d.price_list_rate = r.message.price_list_rate;
+						frappe.model.set_value(d.doctype, d.name, "rate", d.price_list_rate);
+					}
+
+					refresh_field("items");
 				}
 			}
 		});
@@ -242,7 +253,7 @@
 			fields: [
 				{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
 					options:"BOM", reqd: 1, get_query: function() {
-						return {filters: { docstatus:1 }};
+						return {filters: { docstatus:1, "is_active": 1 }};
 					}},
 				{"fieldname":"warehouse", "fieldtype":"Link", "label":__("For Warehouse"),
 					options:"Warehouse", reqd: 1},
@@ -427,12 +438,11 @@
 		frm.events.get_item_data(frm, item, false);
 	},
 
-	rate: function(frm, doctype, name) {
+	rate(frm, doctype, name) {
 		const item = locals[doctype][name];
 		item.amount = flt(item.qty) * flt(item.rate);
 		frappe.model.set_value(doctype, name, "amount", item.amount);
 		refresh_field("amount", item.name, item.parentfield);
-		frm.events.get_item_data(frm, item, false);
 	},
 
 	item_code: function(frm, doctype, name) {
@@ -452,7 +462,12 @@
 				set_schedule_date(frm);
 			}
 		}
-	}
+	},
+
+	conversion_factor: function(frm, doctype, name) {
+		const item = locals[doctype][name];
+		frm.events.get_item_data(frm, item, false);
+	},
 });
 
 erpnext.buying.MaterialRequestController = class MaterialRequestController extends erpnext.buying.BuyingController {
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index 5dc07c9..c705d59 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -35,6 +35,7 @@
   "received_qty",
   "rate_and_amount_section_break",
   "rate",
+  "price_list_rate",
   "col_break3",
   "amount",
   "accounting_details_section",
@@ -197,12 +198,14 @@
    "fieldname": "rate",
    "fieldtype": "Currency",
    "label": "Rate",
+   "options": "Company:company:default_currency",
    "print_hide": 1
   },
   {
    "fieldname": "amount",
    "fieldtype": "Currency",
    "label": "Amount",
+   "options": "Company:company:default_currency",
    "print_hide": 1,
    "read_only": 1
   },
@@ -473,13 +476,22 @@
    "fieldtype": "Link",
    "label": "WIP Composite Asset",
    "options": "Asset"
+  },
+  {
+   "fieldname": "price_list_rate",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Price List Rate",
+   "options": "currency",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-14 18:37:59.599115",
+ "modified": "2024-02-26 18:30:03.684872",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Material Request Item",
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.py b/erpnext/stock/doctype/material_request_item/material_request_item.py
index 2bed596..d23d041 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.py
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.py
@@ -41,6 +41,7 @@
 		parent: DF.Data
 		parentfield: DF.Data
 		parenttype: DF.Data
+		price_list_rate: DF.Currency
 		production_plan: DF.Link | None
 		project: DF.Link | None
 		projected_qty: DF.Float
diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json
index 5dd8934..1daf679 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.json
+++ b/erpnext/stock/doctype/packed_item/packed_item.json
@@ -20,9 +20,12 @@
   "uom",
   "section_break_9",
   "pick_serial_and_batch",
-  "serial_and_batch_bundle",
-  "serial_no",
+  "use_serial_batch_fields",
   "column_break_11",
+  "serial_and_batch_bundle",
+  "section_break_bgys",
+  "serial_no",
+  "column_break_qlha",
   "batch_no",
   "actual_batch_qty",
   "section_break_13",
@@ -118,10 +121,10 @@
    "fieldtype": "Section Break"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "serial_no",
    "fieldtype": "Text",
-   "label": "Serial No",
-   "read_only": 1
+   "label": "Serial No"
   },
   {
    "fieldname": "column_break_11",
@@ -131,8 +134,7 @@
    "fieldname": "batch_no",
    "fieldtype": "Link",
    "label": "Batch No",
-   "options": "Batch",
-   "read_only": 1
+   "options": "Batch"
   },
   {
    "fieldname": "section_break_13",
@@ -259,6 +261,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
@@ -267,16 +270,32 @@
    "print_hide": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "pick_serial_and_batch",
    "fieldtype": "Button",
    "label": "Pick Serial / Batch No"
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
+   "fieldname": "section_break_bgys",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_qlha",
+   "fieldtype": "Column Break"
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-04-28 13:16:38.460806",
+ "modified": "2024-02-04 16:30:44.263964",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Packed Item",
diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py
index ed667c2..c5fed0d 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.py
+++ b/erpnext/stock/doctype/packed_item/packed_item.py
@@ -47,6 +47,7 @@
 		serial_no: DF.Text | None
 		target_warehouse: DF.Link | None
 		uom: DF.Link | None
+		use_serial_batch_fields: DF.Check
 		warehouse: DF.Link | None
 	# end: auto-generated types
 
@@ -226,6 +227,9 @@
 	bin = get_packed_item_bin_qty(packing_item.item_code, pi_row.warehouse)
 	pi_row.actual_qty = flt(bin.get("actual_qty"))
 	pi_row.projected_qty = flt(bin.get("projected_qty"))
+	pi_row.use_serial_batch_fields = frappe.db.get_single_value(
+		"Stock Settings", "use_serial_batch_fields"
+	)
 
 
 def update_packed_item_price_data(pi_row, item_data, doc):
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index afd6ce8..3a5daa1 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -16,7 +16,6 @@
 		frm.set_query('parent_warehouse', () => {
 			return {
 				filters: {
-					'is_group': 1,
 					'company': frm.doc.company
 				}
 			};
@@ -78,6 +77,9 @@
 				},
 				freeze: 1,
 				freeze_message: __("Setting Item Locations..."),
+				callback(r) {
+					refresh_field("locations");
+				}
 			});
 		}
 	},
@@ -328,7 +330,8 @@
 									let qty = Math.abs(r.total_qty);
 									frappe.model.set_value(item.doctype, item.name, {
 										"serial_and_batch_bundle": r.name,
-										"qty": qty
+										"use_serial_batch_fields": 0,
+										"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
 									});
 								}
 							}
diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json
index 7259dc0..0c47434 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.json
+++ b/erpnext/stock/doctype/pick_list/pick_list.json
@@ -16,6 +16,7 @@
   "for_qty",
   "column_break_4",
   "parent_warehouse",
+  "consider_rejected_warehouses",
   "get_item_locations",
   "section_break_6",
   "scan_barcode",
@@ -51,7 +52,7 @@
    "description": "Items under this warehouse will be suggested",
    "fieldname": "parent_warehouse",
    "fieldtype": "Link",
-   "label": "Parent Warehouse",
+   "label": "Warehouse",
    "options": "Warehouse"
   },
   {
@@ -184,11 +185,18 @@
    "report_hide": 1,
    "reqd": 1,
    "search_index": 1
+  },
+  {
+   "default": "0",
+   "description": "Enable it if users want to consider rejected materials to dispatch.",
+   "fieldname": "consider_rejected_warehouses",
+   "fieldtype": "Check",
+   "label": "Consider Rejected Warehouses"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-24 10:33:43.244476",
+ "modified": "2024-02-02 16:17:44.877426",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Pick List",
@@ -260,4 +268,4 @@
  "sort_order": "DESC",
  "states": [],
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 758448a..8a1f79d 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -13,7 +13,7 @@
 from frappe.query_builder import Case
 from frappe.query_builder.custom import GROUP_CONCAT
 from frappe.query_builder.functions import Coalesce, Locate, Replace, Sum
-from frappe.utils import cint, floor, flt
+from frappe.utils import ceil, cint, floor, flt
 from frappe.utils.nestedset import get_descendants_of
 
 from erpnext.selling.doctype.sales_order.sales_order import (
@@ -73,6 +73,10 @@
 		self.update_status()
 		self.set_item_locations()
 
+		if self.get("locations"):
+			self.validate_sales_order_percentage()
+
+	def validate_sales_order_percentage(self):
 		# set percentage picked in SO
 		for location in self.get("locations"):
 			if (
@@ -122,11 +126,42 @@
 
 	def on_submit(self):
 		self.validate_serial_and_batch_bundle()
+		self.make_bundle_using_old_serial_batch_fields()
 		self.update_status()
 		self.update_bundle_picked_qty()
 		self.update_reference_qty()
 		self.update_sales_order_picking_status()
 
+	def make_bundle_using_old_serial_batch_fields(self):
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+		for row in self.locations:
+			if not row.serial_no and not row.batch_no:
+				continue
+
+			if not row.use_serial_batch_fields and (row.serial_no or row.batch_no):
+				frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle"))
+
+			if row.use_serial_batch_fields and (not row.serial_and_batch_bundle):
+				sn_doc = SerialBatchCreation(
+					{
+						"item_code": row.item_code,
+						"warehouse": row.warehouse,
+						"voucher_type": self.doctype,
+						"voucher_no": self.name,
+						"voucher_detail_no": row.name,
+						"qty": row.stock_qty,
+						"type_of_transaction": "Outward",
+						"company": self.company,
+						"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
+						"batches": frappe._dict({row.batch_no: row.stock_qty}) if row.batch_no else None,
+						"batch_no": row.batch_no,
+					}
+				).make_serial_and_batch_bundle()
+
+				row.serial_and_batch_bundle = sn_doc.name
+				row.db_set("serial_and_batch_bundle", sn_doc.name)
+
 	def on_update_after_submit(self) -> None:
 		if self.has_reserved_stock():
 			msg = _(
@@ -156,6 +191,7 @@
 					{"is_cancelled": 1, "voucher_no": ""},
 				)
 
+				frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle).cancel()
 				row.db_set("serial_and_batch_bundle", None)
 
 	def on_update(self):
@@ -316,15 +352,14 @@
 		picked_items_details = self.get_picked_items_details(items)
 		self.item_location_map = frappe._dict()
 
-		from_warehouses = None
+		from_warehouses = [self.parent_warehouse] if self.parent_warehouse else []
 		if self.parent_warehouse:
-			from_warehouses = get_descendants_of("Warehouse", self.parent_warehouse)
+			from_warehouses.extend(get_descendants_of("Warehouse", self.parent_warehouse))
 
 		# Create replica before resetting, to handle empty table on update after submit.
 		locations_replica = self.get("locations")
 
 		# reset
-		self.remove_serial_and_batch_bundle()
 		self.delete_key("locations")
 		updated_locations = frappe._dict()
 		for item_doc in items:
@@ -338,6 +373,7 @@
 					self.item_count_map.get(item_code),
 					self.company,
 					picked_item_details=picked_items_details.get(item_code),
+					consider_rejected_warehouses=self.consider_rejected_warehouses,
 				),
 			)
 
@@ -639,13 +675,19 @@
 			if not stock_qty:
 				break
 
+		serial_nos = None
+		if item_location.serial_nos:
+			serial_nos = "\n".join(item_location.serial_nos[0 : cint(stock_qty)])
+
 		locations.append(
 			frappe._dict(
 				{
 					"qty": qty,
 					"stock_qty": stock_qty,
 					"warehouse": item_location.warehouse,
-					"serial_and_batch_bundle": item_location.serial_and_batch_bundle,
+					"serial_no": serial_nos,
+					"batch_no": item_location.batch_no,
+					"use_serial_batch_fields": 1,
 				}
 			)
 		)
@@ -673,6 +715,7 @@
 	company,
 	ignore_validation=False,
 	picked_item_details=None,
+	consider_rejected_warehouses=False,
 ):
 	locations = []
 	total_picked_qty = (
@@ -681,17 +724,41 @@
 	has_serial_no = frappe.get_cached_value("Item", item_code, "has_serial_no")
 	has_batch_no = frappe.get_cached_value("Item", item_code, "has_batch_no")
 
-	if has_serial_no:
+	if has_batch_no and has_serial_no:
+		locations = get_available_item_locations_for_serial_and_batched_item(
+			item_code,
+			from_warehouses,
+			required_qty,
+			company,
+			total_picked_qty,
+			consider_rejected_warehouses=consider_rejected_warehouses,
+		)
+	elif has_serial_no:
 		locations = get_available_item_locations_for_serialized_item(
-			item_code, from_warehouses, required_qty, company, total_picked_qty
+			item_code,
+			from_warehouses,
+			required_qty,
+			company,
+			total_picked_qty,
+			consider_rejected_warehouses=consider_rejected_warehouses,
 		)
 	elif has_batch_no:
 		locations = get_available_item_locations_for_batched_item(
-			item_code, from_warehouses, required_qty, company, total_picked_qty
+			item_code,
+			from_warehouses,
+			required_qty,
+			company,
+			total_picked_qty,
+			consider_rejected_warehouses=consider_rejected_warehouses,
 		)
 	else:
 		locations = get_available_item_locations_for_other_item(
-			item_code, from_warehouses, required_qty, company, total_picked_qty
+			item_code,
+			from_warehouses,
+			required_qty,
+			company,
+			total_picked_qty,
+			consider_rejected_warehouses=consider_rejected_warehouses,
 		)
 
 	total_qty_available = sum(location.get("qty") for location in locations)
@@ -724,8 +791,56 @@
 	return locations
 
 
+def get_available_item_locations_for_serial_and_batched_item(
+	item_code,
+	from_warehouses,
+	required_qty,
+	company,
+	total_picked_qty=0,
+	consider_rejected_warehouses=False,
+):
+	# Get batch nos by FIFO
+	locations = get_available_item_locations_for_batched_item(
+		item_code,
+		from_warehouses,
+		required_qty,
+		company,
+		consider_rejected_warehouses=consider_rejected_warehouses,
+	)
+
+	if locations:
+		sn = frappe.qb.DocType("Serial No")
+		conditions = (sn.item_code == item_code) & (sn.company == company)
+
+		for location in locations:
+			location.qty = (
+				required_qty if location.qty > required_qty else location.qty
+			)  # if extra qty in batch
+
+			serial_nos = (
+				frappe.qb.from_(sn)
+				.select(sn.name)
+				.where(
+					(conditions) & (sn.batch_no == location.batch_no) & (sn.warehouse == location.warehouse)
+				)
+				.orderby(sn.creation)
+				.limit(ceil(location.qty + total_picked_qty))
+			).run(as_dict=True)
+
+			serial_nos = [sn.name for sn in serial_nos]
+			location.serial_nos = serial_nos
+			location.qty = len(serial_nos)
+
+	return locations
+
+
 def get_available_item_locations_for_serialized_item(
-	item_code, from_warehouses, required_qty, company, total_picked_qty=0
+	item_code,
+	from_warehouses,
+	required_qty,
+	company,
+	total_picked_qty=0,
+	consider_rejected_warehouses=False,
 ):
 	picked_serial_nos = get_picked_serial_nos(item_code, from_warehouses)
 
@@ -742,6 +857,10 @@
 	else:
 		query = query.where(Coalesce(sn.warehouse, "") != "")
 
+	if not consider_rejected_warehouses:
+		if rejected_warehouses := get_rejected_warehouses():
+			query = query.where(sn.warehouse.notin(rejected_warehouses))
+
 	serial_nos = query.run(as_list=True)
 
 	warehouse_serial_nos_map = frappe._dict()
@@ -757,28 +876,16 @@
 		picked_qty -= 1
 
 	locations = []
+
 	for warehouse, serial_nos in warehouse_serial_nos_map.items():
 		qty = len(serial_nos)
 
-		bundle_doc = SerialBatchCreation(
-			{
-				"item_code": item_code,
-				"warehouse": warehouse,
-				"voucher_type": "Pick List",
-				"total_qty": qty * -1,
-				"serial_nos": serial_nos,
-				"type_of_transaction": "Outward",
-				"company": company,
-				"do_not_submit": True,
-			}
-		).make_serial_and_batch_bundle()
-
 		locations.append(
 			{
 				"qty": qty,
 				"warehouse": warehouse,
 				"item_code": item_code,
-				"serial_and_batch_bundle": bundle_doc.name,
+				"serial_nos": serial_nos,
 			}
 		)
 
@@ -786,7 +893,12 @@
 
 
 def get_available_item_locations_for_batched_item(
-	item_code, from_warehouses, required_qty, company, total_picked_qty=0
+	item_code,
+	from_warehouses,
+	required_qty,
+	company,
+	total_picked_qty=0,
+	consider_rejected_warehouses=False,
 ):
 	locations = []
 	data = get_auto_batch_nos(
@@ -801,42 +913,42 @@
 	)
 
 	warehouse_wise_batches = frappe._dict()
+	rejected_warehouses = get_rejected_warehouses()
+
 	for d in data:
+		if (
+			not consider_rejected_warehouses and rejected_warehouses and d.warehouse in rejected_warehouses
+		):
+			continue
+
 		if d.warehouse not in warehouse_wise_batches:
 			warehouse_wise_batches.setdefault(d.warehouse, defaultdict(float))
 
 		warehouse_wise_batches[d.warehouse][d.batch_no] += d.qty
 
 	for warehouse, batches in warehouse_wise_batches.items():
-		qty = sum(batches.values())
-
-		bundle_doc = SerialBatchCreation(
-			{
-				"item_code": item_code,
-				"warehouse": warehouse,
-				"voucher_type": "Pick List",
-				"total_qty": qty * -1,
-				"batches": batches,
-				"type_of_transaction": "Outward",
-				"company": company,
-				"do_not_submit": True,
-			}
-		).make_serial_and_batch_bundle()
-
-		locations.append(
-			{
-				"qty": qty,
-				"warehouse": warehouse,
-				"item_code": item_code,
-				"serial_and_batch_bundle": bundle_doc.name,
-			}
-		)
+		for batch_no, qty in batches.items():
+			locations.append(
+				frappe._dict(
+					{
+						"qty": qty,
+						"warehouse": warehouse,
+						"item_code": item_code,
+						"batch_no": batch_no,
+					}
+				)
+			)
 
 	return locations
 
 
 def get_available_item_locations_for_other_item(
-	item_code, from_warehouses, required_qty, company, total_picked_qty=0
+	item_code,
+	from_warehouses,
+	required_qty,
+	company,
+	total_picked_qty=0,
+	consider_rejected_warehouses=False,
 ):
 	bin = frappe.qb.DocType("Bin")
 	query = (
@@ -853,6 +965,10 @@
 		wh = frappe.qb.DocType("Warehouse")
 		query = query.from_(wh).where((bin.warehouse == wh.name) & (wh.company == company))
 
+	if not consider_rejected_warehouses:
+		if rejected_warehouses := get_rejected_warehouses():
+			query = query.where(bin.warehouse.notin(rejected_warehouses))
+
 	item_locations = query.run(as_dict=True)
 
 	return item_locations
@@ -1174,3 +1290,15 @@
 	item.serial_no = location.serial_no
 	item.batch_no = location.batch_no
 	item.material_request_item = location.material_request_item
+
+
+def get_rejected_warehouses():
+	if not hasattr(frappe.local, "rejected_warehouses"):
+		frappe.local.rejected_warehouses = []
+
+	if not frappe.local.rejected_warehouses:
+		frappe.local.rejected_warehouses = frappe.get_all(
+			"Warehouse", filters={"is_rejected_warehouse": 1}, pluck="name"
+		)
+
+	return frappe.local.rejected_warehouses
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 322b0b4..cffd0d2 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -217,6 +217,8 @@
 		)
 
 		pick_list.save()
+		pick_list.submit()
+
 		self.assertEqual(pick_list.locations[0].item_code, "_Test Serialized Item")
 		self.assertEqual(pick_list.locations[0].warehouse, "_Test Warehouse - _TC")
 		self.assertEqual(pick_list.locations[0].qty, 5)
@@ -239,7 +241,7 @@
 		pr1 = make_purchase_receipt(item_code="Batched Item", qty=1, rate=100.0)
 
 		pr1.load_from_db()
-		oldest_batch_no = pr1.items[0].batch_no
+		oldest_batch_no = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle)
 
 		pr2 = make_purchase_receipt(item_code="Batched Item", qty=2, rate=100.0)
 
@@ -302,6 +304,8 @@
 			}
 		)
 		pick_list.set_item_locations()
+		pick_list.submit()
+		pick_list.reload()
 
 		self.assertEqual(
 			get_batch_from_bundle(pick_list.locations[0].serial_and_batch_bundle), oldest_batch_no
@@ -310,6 +314,7 @@
 			get_serial_nos_from_bundle(pick_list.locations[0].serial_and_batch_bundle), oldest_serial_nos
 		)
 
+		pick_list.cancel()
 		pr1.cancel()
 		pr2.cancel()
 
@@ -671,29 +676,22 @@
 
 		so = make_sales_order(item_code=item, qty=25.0, rate=100)
 		pl = create_pick_list(so.name)
+		pl.submit()
 		# pick half the qty
 		for loc in pl.locations:
 			self.assertEqual(loc.qty, 25.0)
 			self.assertTrue(loc.serial_and_batch_bundle)
 
-			data = frappe.get_all(
-				"Serial and Batch Entry",
-				fields=["qty", "batch_no"],
-				filters={"parent": loc.serial_and_batch_bundle},
-			)
-
-			for d in data:
-				self.assertEqual(d.batch_no, "PICKLT-000001")
-				self.assertEqual(d.qty, 25.0 * -1)
-
 		pl.save()
 		pl.submit()
 
 		so1 = make_sales_order(item_code=item, qty=10.0, rate=100)
-		pl = create_pick_list(so1.name)
+		pl1 = create_pick_list(so1.name)
+		pl1.submit()
+
 		# pick half the qty
-		for loc in pl.locations:
-			self.assertEqual(loc.qty, 10.0)
+		for loc in pl1.locations:
+			self.assertEqual(loc.qty, 5.0)
 			self.assertTrue(loc.serial_and_batch_bundle)
 
 			data = frappe.get_all(
@@ -709,8 +707,7 @@
 				elif d.batch_no == "PICKLT-000002":
 					self.assertEqual(d.qty, 5.0 * -1)
 
-		pl.save()
-		pl.submit()
+		pl1.cancel()
 		pl.cancel()
 
 	def test_picklist_for_serial_item(self):
@@ -723,6 +720,7 @@
 
 		so = make_sales_order(item_code=item, qty=25.0, rate=100)
 		pl = create_pick_list(so.name)
+		pl.submit()
 		picked_serial_nos = []
 		# pick half the qty
 		for loc in pl.locations:
@@ -736,13 +734,11 @@
 			picked_serial_nos = [d.serial_no for d in data]
 			self.assertEqual(len(picked_serial_nos), 25)
 
-		pl.save()
-		pl.submit()
-
 		so1 = make_sales_order(item_code=item, qty=10.0, rate=100)
-		pl = create_pick_list(so1.name)
+		pl1 = create_pick_list(so1.name)
+		pl1.submit()
 		# pick half the qty
-		for loc in pl.locations:
+		for loc in pl1.locations:
 			self.assertEqual(loc.qty, 10.0)
 			self.assertTrue(loc.serial_and_batch_bundle)
 
@@ -756,8 +752,7 @@
 			for d in data:
 				self.assertTrue(d.serial_no not in picked_serial_nos)
 
-		pl.save()
-		pl.submit()
+		pl1.cancel()
 		pl.cancel()
 
 	def test_picklist_with_bundles(self):
diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
index e8e4afc..962fa9f 100644
--- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json
+++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
@@ -24,8 +24,11 @@
   "serial_no_and_batch_section",
   "pick_serial_and_batch",
   "serial_and_batch_bundle",
-  "serial_no",
+  "use_serial_batch_fields",
   "column_break_20",
+  "section_break_ecxc",
+  "serial_no",
+  "column_break_belw",
   "batch_no",
   "column_break_15",
   "sales_order",
@@ -72,19 +75,17 @@
    "read_only": 1
   },
   {
-   "depends_on": "serial_no",
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "serial_no",
    "fieldtype": "Small Text",
-   "label": "Serial No",
-   "read_only": 1
+   "label": "Serial No"
   },
   {
-   "depends_on": "batch_no",
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "batch_no",
    "fieldtype": "Link",
    "label": "Batch No",
    "options": "Batch",
-   "read_only": 1,
    "search_index": 1
   },
   {
@@ -195,6 +196,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
@@ -204,6 +206,7 @@
    "search_index": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "pick_serial_and_batch",
    "fieldtype": "Button",
    "label": "Pick Serial / Batch No"
@@ -218,11 +221,26 @@
    "print_hide": 1,
    "read_only": 1,
    "report_hide": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
+   "fieldname": "section_break_ecxc",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_belw",
+   "fieldtype": "Column Break"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2023-07-26 12:54:15.785962",
+ "modified": "2024-02-04 16:12:16.257951",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Pick List Item",
diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.py b/erpnext/stock/doctype/pick_list_item/pick_list_item.py
index 6e5a94e..f3f6298 100644
--- a/erpnext/stock/doctype/pick_list_item/pick_list_item.py
+++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.py
@@ -37,6 +37,7 @@
 		stock_reserved_qty: DF.Float
 		stock_uom: DF.Link | None
 		uom: DF.Link | None
+		use_serial_batch_fields: DF.Check
 		warehouse: DF.Link | None
 	# end: auto-generated types
 
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index bf6080b..1e2d8eb 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -369,6 +369,7 @@
 		else:
 			self.db_set("status", "Completed")
 
+		self.make_bundle_using_old_serial_batch_fields()
 		# Updating stock ledger should always be called after updating prevdoc status,
 		# because updating ordered qty, reserved_qty_for_subcontract in bin
 		# depends upon updated ordered qty in PO
@@ -685,9 +686,7 @@
 						)
 
 					stock_value_diff = (
-						flt(d.base_net_amount)
-						+ flt(d.item_tax_amount / self.conversion_rate)
-						+ flt(d.landed_cost_voucher_amount)
+						flt(d.base_net_amount) + flt(d.item_tax_amount) + flt(d.landed_cost_voucher_amount)
 					)
 				elif warehouse_account.get(d.warehouse):
 					stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse)
@@ -1360,16 +1359,16 @@
 	for lcv in landed_cost_vouchers:
 		landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent)
 
+		based_on_field = None
 		# Use amount field for total item cost for manually cost distributed LCVs
-		if landed_cost_voucher_doc.distribute_charges_based_on == "Distribute Manually":
-			based_on_field = "amount"
-		else:
+		if landed_cost_voucher_doc.distribute_charges_based_on != "Distribute Manually":
 			based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
 
 		total_item_cost = 0
 
-		for item in landed_cost_voucher_doc.items:
-			total_item_cost += item.get(based_on_field)
+		if based_on_field:
+			for item in landed_cost_voucher_doc.items:
+				total_item_cost += item.get(based_on_field)
 
 		for item in landed_cost_voucher_doc.items:
 			if item.receipt_document == purchase_document:
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 65c08c1..d542601 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -2230,6 +2230,218 @@
 		pr_doc.reload()
 		self.assertFalse(pr_doc.items[0].from_warehouse)
 
+	def test_use_serial_batch_fields_for_serial_nos(self):
+		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+		from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
+			create_stock_reconciliation,
+		)
+
+		frappe.db.set_single_value(
+			"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 0
+		)
+
+		item_code = make_item(
+			"_Test Use Serial Fields Item Serial Item",
+			properties={"has_serial_no": 1, "serial_no_series": "SNU-TSFISI-.#####"},
+		).name
+
+		serial_nos = [
+			"SNU-TSFISI-000011",
+			"SNU-TSFISI-000012",
+			"SNU-TSFISI-000013",
+			"SNU-TSFISI-000014",
+			"SNU-TSFISI-000015",
+		]
+
+		pr = make_purchase_receipt(
+			item_code=item_code,
+			qty=5,
+			serial_no="\n".join(serial_nos),
+			use_serial_batch_fields=1,
+			rate=100,
+		)
+
+		self.assertEqual(pr.items[0].use_serial_batch_fields, 1)
+		self.assertTrue(pr.items[0].serial_no)
+		self.assertTrue(pr.items[0].serial_and_batch_bundle)
+
+		sbb_doc = frappe.get_doc("Serial and Batch Bundle", pr.items[0].serial_and_batch_bundle)
+
+		for row in sbb_doc.entries:
+			self.assertTrue(row.serial_no in serial_nos)
+
+		serial_nos.remove("SNU-TSFISI-000015")
+
+		sr = create_stock_reconciliation(
+			item_code=item_code,
+			serial_no="\n".join(serial_nos),
+			qty=4,
+			warehouse=pr.items[0].warehouse,
+			use_serial_batch_fields=1,
+			do_not_submit=True,
+		)
+		sr.reload()
+
+		serial_nos = get_serial_nos(sr.items[0].current_serial_no)
+		self.assertEqual(len(serial_nos), 5)
+		self.assertEqual(sr.items[0].current_qty, 5)
+
+		new_serial_nos = get_serial_nos(sr.items[0].serial_no)
+		self.assertEqual(len(new_serial_nos), 4)
+		self.assertEqual(sr.items[0].qty, 4)
+		self.assertEqual(sr.items[0].use_serial_batch_fields, 1)
+		self.assertFalse(sr.items[0].current_serial_and_batch_bundle)
+		self.assertFalse(sr.items[0].serial_and_batch_bundle)
+		self.assertTrue(sr.items[0].current_serial_no)
+		sr.submit()
+
+		sr.reload()
+		self.assertTrue(sr.items[0].current_serial_and_batch_bundle)
+		self.assertTrue(sr.items[0].serial_and_batch_bundle)
+
+		serial_no_status = frappe.db.get_value("Serial No", "SNU-TSFISI-000015", "status")
+
+		self.assertTrue(serial_no_status != "Active")
+
+		dn = create_delivery_note(
+			item_code=item_code,
+			qty=4,
+			serial_no="\n".join(new_serial_nos),
+			use_serial_batch_fields=1,
+		)
+
+		self.assertTrue(dn.items[0].serial_and_batch_bundle)
+		self.assertEqual(dn.items[0].qty, 4)
+		doc = frappe.get_doc("Serial and Batch Bundle", dn.items[0].serial_and_batch_bundle)
+		for row in doc.entries:
+			self.assertTrue(row.serial_no in new_serial_nos)
+
+		for sn in new_serial_nos:
+			serial_no_status = frappe.db.get_value("Serial No", sn, "status")
+			self.assertTrue(serial_no_status != "Active")
+
+		frappe.db.set_single_value(
+			"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 1
+		)
+
+	def test_sle_qty_after_transaction(self):
+		item = make_item(
+			"_Test Item Qty After Transaction",
+			properties={"is_stock_item": 1, "valuation_method": "FIFO"},
+		).name
+
+		posting_date = today()
+		posting_time = nowtime()
+
+		# Step 1: Create Purchase Receipt
+		pr = make_purchase_receipt(
+			item_code=item,
+			qty=1,
+			rate=100,
+			posting_date=posting_date,
+			posting_time=posting_time,
+			do_not_save=1,
+		)
+
+		for i in range(9):
+			pr.append(
+				"items",
+				{
+					"item_code": item,
+					"qty": 1,
+					"rate": 100,
+					"warehouse": pr.items[0].warehouse,
+					"cost_center": pr.items[0].cost_center,
+					"expense_account": pr.items[0].expense_account,
+					"uom": pr.items[0].uom,
+					"stock_uom": pr.items[0].stock_uom,
+					"conversion_factor": pr.items[0].conversion_factor,
+				},
+			)
+
+		self.assertEqual(len(pr.items), 10)
+		pr.save()
+		pr.submit()
+
+		data = frappe.get_all(
+			"Stock Ledger Entry",
+			fields=["qty_after_transaction", "creation", "posting_datetime"],
+			filters={"voucher_no": pr.name, "is_cancelled": 0},
+			order_by="creation",
+		)
+
+		for index, d in enumerate(data):
+			self.assertEqual(d.qty_after_transaction, 1 + index)
+
+		# Step 2: Create Purchase Receipt
+		pr = make_purchase_receipt(
+			item_code=item,
+			qty=1,
+			rate=100,
+			posting_date=posting_date,
+			posting_time=posting_time,
+			do_not_save=1,
+		)
+
+		for i in range(9):
+			pr.append(
+				"items",
+				{
+					"item_code": item,
+					"qty": 1,
+					"rate": 100,
+					"warehouse": pr.items[0].warehouse,
+					"cost_center": pr.items[0].cost_center,
+					"expense_account": pr.items[0].expense_account,
+					"uom": pr.items[0].uom,
+					"stock_uom": pr.items[0].stock_uom,
+					"conversion_factor": pr.items[0].conversion_factor,
+				},
+			)
+
+		self.assertEqual(len(pr.items), 10)
+		pr.save()
+		pr.submit()
+
+		data = frappe.get_all(
+			"Stock Ledger Entry",
+			fields=["qty_after_transaction", "creation", "posting_datetime"],
+			filters={"voucher_no": pr.name, "is_cancelled": 0},
+			order_by="creation",
+		)
+
+		for index, d in enumerate(data):
+			self.assertEqual(d.qty_after_transaction, 11 + index)
+
+	def test_auto_set_batch_based_on_bundle(self):
+		item_code = make_item(
+			"_Test Auto Set Batch Based on Bundle",
+			properties={
+				"has_batch_no": 1,
+				"batch_number_series": "BATCH-BNU-TASBBB-.#####",
+				"create_new_batch": 1,
+			},
+		).name
+
+		frappe.db.set_single_value(
+			"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 0
+		)
+
+		pr = make_purchase_receipt(
+			item_code=item_code,
+			qty=5,
+			rate=100,
+		)
+
+		self.assertTrue(pr.items[0].batch_no)
+		batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
+		self.assertEqual(pr.items[0].batch_no, batch_no)
+
+		frappe.db.set_single_value(
+			"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 1
+		)
+
 
 def prepare_data_for_internal_transfer():
 	from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
@@ -2399,7 +2611,7 @@
 	uom = args.uom or frappe.db.get_value("Item", item_code, "stock_uom") or "_Test UOM"
 
 	bundle_id = None
-	if args.get("batch_no") or args.get("serial_no"):
+	if not args.use_serial_batch_fields and (args.get("batch_no") or args.get("serial_no")):
 		batches = {}
 		if args.get("batch_no"):
 			batches = frappe._dict({args.batch_no: qty})
@@ -2441,6 +2653,9 @@
 			"cost_center": args.cost_center
 			or frappe.get_cached_value("Company", pr.company, "cost_center"),
 			"asset_location": args.location or "Test Location",
+			"use_serial_batch_fields": args.use_serial_batch_fields or 0,
+			"serial_no": args.serial_no if args.use_serial_batch_fields else "",
+			"batch_no": args.batch_no if args.use_serial_batch_fields else "",
 		},
 	)
 
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 9bd692a..6b01047 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -94,6 +94,7 @@
   "section_break_45",
   "add_serial_batch_bundle",
   "serial_and_batch_bundle",
+  "use_serial_batch_fields",
   "col_break5",
   "add_serial_batch_for_rejected_qty",
   "rejected_serial_and_batch_bundle",
@@ -1003,6 +1004,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
@@ -1020,24 +1022,22 @@
   {
    "fieldname": "serial_no",
    "fieldtype": "Text",
-   "label": "Serial No",
-   "read_only": 1
+   "label": "Serial No"
   },
   {
    "fieldname": "rejected_serial_no",
    "fieldtype": "Text",
-   "label": "Rejected Serial No",
-   "read_only": 1
+   "label": "Rejected Serial No"
   },
   {
    "fieldname": "batch_no",
    "fieldtype": "Link",
    "label": "Batch No",
    "options": "Batch",
-   "read_only": 1,
    "search_index": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "rejected_serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Rejected Serial and Batch Bundle",
@@ -1045,11 +1045,13 @@
    "options": "Serial and Batch Bundle"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0",
    "fieldname": "add_serial_batch_for_rejected_qty",
    "fieldtype": "Button",
    "label": "Add Serial / Batch No (Rejected Qty)"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "section_break_3vxt",
    "fieldtype": "Section Break"
   },
@@ -1058,6 +1060,7 @@
    "fieldtype": "Column Break"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0",
    "fieldname": "add_serial_batch_bundle",
    "fieldtype": "Button",
    "label": "Add Serial / Batch No"
@@ -1098,12 +1101,18 @@
    "read_only": 1,
    "report_hide": 1,
    "search_index": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-12-25 22:32:09.801965",
+ "modified": "2024-02-04 11:48:06.653771",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py
index aed8d21..3c6dcdc 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py
@@ -99,6 +99,7 @@
 		supplier_part_no: DF.Data | None
 		total_weight: DF.Float
 		uom: DF.Link
+		use_serial_batch_fields: DF.Check
 		valuation_rate: DF.Currency
 		warehouse: DF.Link | None
 		weight_per_unit: DF.Float
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 4f45210..a383798 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -281,6 +281,7 @@
 		repost_gl_entries(doc)
 
 		doc.set_status("Completed")
+		remove_attached_file(doc.name)
 
 	except Exception as e:
 		if frappe.flags.in_test:
@@ -293,9 +294,20 @@
 		doc.log_error("Unable to repost item valuation")
 
 		message = frappe.message_log.pop() if frappe.message_log else ""
+		if isinstance(message, dict):
+			message = message.get("message")
+
 		if traceback:
-			message += "<br>" + "Traceback: <br>" + traceback
-		frappe.db.set_value(doc.doctype, doc.name, "error_log", message)
+			message += "<br><br>" + "<b>Traceback:</b> <br>" + traceback
+
+		frappe.db.set_value(
+			doc.doctype,
+			doc.name,
+			{
+				"error_log": message,
+				"status": "Failed",
+			},
+		)
 
 		outgoing_email_account = frappe.get_cached_value(
 			"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
@@ -309,6 +321,13 @@
 			frappe.db.commit()
 
 
+def remove_attached_file(docname):
+	if file_name := frappe.db.get_value(
+		"File", {"attached_to_name": docname, "attached_to_doctype": "Repost Item Valuation"}, "name"
+	):
+		frappe.delete_doc("File", file_name, delete_permanently=True)
+
+
 def repost_sl_entries(doc):
 	if doc.based_on == "Transaction":
 		repost_future_sle(
diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
index 5b76e44..f96a612 100644
--- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
@@ -420,3 +420,38 @@
 
 		self.assertRaises(frappe.ValidationError, riv.save)
 		doc.cancel()
+
+	def test_remove_attached_file(self):
+		item_code = make_item("_Test Remove Attached File Item", properties={"is_stock_item": 1})
+
+		make_purchase_receipt(
+			item_code=item_code,
+			qty=1,
+			rate=100,
+		)
+
+		pr1 = make_purchase_receipt(
+			item_code=item_code,
+			qty=1,
+			rate=100,
+			posting_date=add_days(today(), days=-1),
+		)
+
+		if docname := frappe.db.exists("Repost Item Valuation", {"voucher_no": pr1.name}):
+			self.assertFalse(
+				frappe.db.get_value(
+					"File",
+					{"attached_to_doctype": "Repost Item Valuation", "attached_to_name": docname},
+					"name",
+				)
+			)
+		else:
+			repost_entries = create_item_wise_repost_entries(pr1.doctype, pr1.name)
+			for entry in repost_entries:
+				self.assertFalse(
+					frappe.db.get_value(
+						"File",
+						{"attached_to_doctype": "Repost Item Valuation", "attached_to_name": entry.name},
+						"name",
+					)
+				)
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
index 91b7430..1f7bb4d 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
@@ -207,13 +207,24 @@
 			};
 		});
 
-		frm.set_query('batch_no', 'entries', () => {
-			return {
-				filters: {
-					item: frm.doc.item_code,
-					disabled: 0,
+		frm.set_query('batch_no', 'entries', (doc) => {
+
+			if (doc.type_of_transaction ==="Outward") {
+				return {
+					query : "erpnext.controllers.queries.get_batch_no",
+					filters: {
+						item_code: doc.item_code,
+						warehouse: doc.warehouse,
+					}
 				}
-			};
+			} else {
+				return {
+					filters: {
+						item: doc.item_code,
+						disabled: 0,
+					}
+				};
+			}
 		});
 
 		frm.set_query('warehouse', 'entries', () => {
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 9cad8f6..b6e4d6f 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
@@ -257,9 +257,9 @@
 				if sn_obj.batch_avg_rate.get(d.batch_no):
 					d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no))
 
-				available_qty = flt(sn_obj.available_qty.get(d.batch_no))
+				available_qty = flt(sn_obj.available_qty.get(d.batch_no), d.precision("qty"))
 				if self.docstatus == 1:
-					available_qty += flt(d.qty)
+					available_qty += flt(d.qty, d.precision("qty"))
 
 				if not allow_negative_stock:
 					self.validate_negative_batch(d.batch_no, available_qty)
@@ -1117,7 +1117,7 @@
 	if isinstance(data, list):
 		return data
 
-	return [s.strip() for s in cstr(data).strip().upper().replace(",", "\n").split("\n") if s.strip()]
+	return [s.strip() for s in cstr(data).strip().replace(",", "\n").split("\n") if s.strip()]
 
 
 @frappe.whitelist()
@@ -1256,7 +1256,7 @@
 
 
 def get_type_of_transaction(parent_doc, child_row):
-	type_of_transaction = child_row.type_of_transaction
+	type_of_transaction = child_row.get("type_of_transaction")
 	if parent_doc.get("doctype") == "Stock Entry":
 		type_of_transaction = "Outward" if child_row.s_warehouse else "Inward"
 
@@ -1384,6 +1384,8 @@
 
 	filters = {"item_code": kwargs.item_code}
 
+	# ignore_warehouse is used for backdated stock transactions
+	# There might be chances that the serial no not exists in the warehouse during backdated stock transactions
 	if not kwargs.get("ignore_warehouse"):
 		filters["warehouse"] = ("is", "set")
 		if kwargs.warehouse:
@@ -1677,7 +1679,10 @@
 			query = query.where(sb_entry.batch_no == kwargs.batch_no)
 
 	if kwargs.warehouse:
-		query = query.where(sre.warehouse == kwargs.warehouse)
+		if isinstance(kwargs.warehouse, list):
+			query = query.where(sre.warehouse.isin(kwargs.warehouse))
+		else:
+			query = query.where(sre.warehouse == kwargs.warehouse)
 
 	if kwargs.ignore_voucher_nos:
 		query = query.where(sre.name.notin(kwargs.ignore_voucher_nos))
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
index 0d453fb..b932c13 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
@@ -4,8 +4,8 @@
 import json
 
 import frappe
-from frappe.tests.utils import FrappeTestCase
-from frappe.utils import add_days, add_to_date, flt, nowdate, nowtime, today
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils import flt, nowtime, today
 
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
@@ -136,6 +136,7 @@
 
 	def test_old_batch_valuation(self):
 		frappe.flags.ignore_serial_batch_bundle_validation = True
+		frappe.flags.use_serial_and_batch_fields = True
 		batch_item_code = "Old Batch Item Valuation 1"
 		make_item(
 			batch_item_code,
@@ -190,6 +191,7 @@
 			doc.flags.ignore_links = True
 			doc.flags.ignore_validate = True
 			doc.submit()
+			doc.reload()
 
 		bundle_doc = make_serial_batch_bundle(
 			{
@@ -240,6 +242,7 @@
 		bundle_doc.submit()
 
 		frappe.flags.ignore_serial_batch_bundle_validation = False
+		frappe.flags.use_serial_and_batch_fields = False
 
 	def test_old_serial_no_valuation(self):
 		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -259,6 +262,7 @@
 		)
 
 		frappe.flags.ignore_serial_batch_bundle_validation = True
+		frappe.flags.use_serial_and_batch_fields = True
 
 		serial_no_id = "Old Serial No 1"
 		if not frappe.db.exists("Serial No", serial_no_id):
@@ -320,6 +324,9 @@
 		for row in bundle_doc.entries:
 			self.assertEqual(flt(row.stock_value_difference, 2), -100.00)
 
+		frappe.flags.ignore_serial_batch_bundle_validation = False
+		frappe.flags.use_serial_and_batch_fields = False
+
 	def test_batch_not_belong_to_serial_no(self):
 		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 
@@ -515,6 +522,24 @@
 		make_serial_nos(item_code, serial_nos)
 		self.assertTrue(frappe.db.exists("Serial No", serial_no_id))
 
+	@change_settings("Stock Settings", {"auto_create_serial_and_batch_bundle_for_outward": 1})
+	def test_duplicate_serial_and_batch_bundle(self):
+		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+
+		item_code = make_item(properties={"is_stock_item": 1, "has_serial_no": 1}).name
+
+		serial_no = f"{item_code}-001"
+		serial_nos = [{"serial_no": serial_no, "qty": 1}]
+		make_serial_nos(item_code, serial_nos)
+
+		pr1 = make_purchase_receipt(item=item_code, qty=1, rate=500, serial_no=[serial_no])
+		pr2 = make_purchase_receipt(item=item_code, qty=1, rate=500, do_not_save=True)
+
+		pr1.reload()
+		pr2.items[0].serial_and_batch_bundle = pr1.items[0].serial_and_batch_bundle
+
+		self.assertRaises(frappe.exceptions.ValidationError, pr2.save)
+
 
 def get_batch_from_bundle(bundle):
 	from erpnext.stock.serial_batch_bundle import get_batch_nos
diff --git a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
index 5de2c2e..844270b 100644
--- a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
+++ b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
@@ -68,7 +68,7 @@
   {
    "fieldname": "incoming_rate",
    "fieldtype": "Float",
-   "label": "Incoming Rate",
+   "label": "Valuation Rate",
    "no_copy": 1,
    "read_only": 1,
    "read_only_depends_on": "eval:parent.type_of_transaction == \"Outward\""
@@ -76,6 +76,7 @@
   {
    "fieldname": "outgoing_rate",
    "fieldtype": "Float",
+   "hidden": 1,
    "label": "Outgoing Rate",
    "no_copy": 1,
    "read_only": 1
@@ -95,6 +96,7 @@
    "default": "0",
    "fieldname": "is_outward",
    "fieldtype": "Check",
+   "hidden": 1,
    "label": "Is Outward",
    "read_only": 1
   },
@@ -120,7 +122,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-12-10 19:47:48.227772",
+ "modified": "2024-02-23 12:44:18.054270",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial and Batch Entry",
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 122664c..5f4f393 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -151,9 +151,7 @@
 	if isinstance(serial_no, list):
 		return serial_no
 
-	return [
-		s.strip() for s in cstr(serial_no).strip().upper().replace(",", "\n").split("\n") if s.strip()
-	]
+	return [s.strip() for s in cstr(serial_no).strip().replace(",", "\n").split("\n") if s.strip()]
 
 
 def clean_serial_no_string(serial_no: str) -> str:
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 8da3e8f..7f79f04 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -543,7 +543,9 @@
 
 		let fields = [
 			{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
-			options:"BOM", reqd: 1, get_query: filters()},
+			options:"BOM", reqd: 1, get_query: () => {
+				return {filters: { docstatus:1, "is_active": 1 }};
+			}},
 			{"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"),
 			options:"Warehouse"},
 			{"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"),
@@ -936,6 +938,7 @@
 		this.toggle_related_fields(this.frm.doc);
 		this.toggle_enable_bom();
 		this.show_stock_ledger();
+		this.set_fields_onload_for_line_item();
 		erpnext.utils.view_serial_batch_nos(this.frm);
 		if (this.frm.doc.docstatus===1 && erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
 			this.show_general_ledger();
@@ -944,6 +947,35 @@
 		erpnext.utils.add_item(this.frm);
 	}
 
+	serial_no(doc, cdt, cdn) {
+		var item = frappe.get_doc(cdt, cdn);
+
+		if (item?.serial_no) {
+			// Replace all occurences of comma with line feed
+			item.serial_no = item.serial_no.replace(/,/g, '\n');
+			item.conversion_factor = item.conversion_factor || 1;
+
+			let valid_serial_nos = [];
+			let serialnos = item.serial_no.split("\n");
+			for (var i = 0; i < serialnos.length; i++) {
+				if (serialnos[i] != "") {
+					valid_serial_nos.push(serialnos[i]);
+				}
+			}
+			frappe.model.set_value(item.doctype, item.name,
+				"qty", valid_serial_nos.length / item.conversion_factor);
+		}
+	}
+
+	set_fields_onload_for_line_item() {
+		if (this.frm.is_new() && this.frm.doc?.items
+			&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1) {
+			this.frm.doc.items.forEach(item => {
+				frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1);
+			})
+		}
+	}
+
 	scan_barcode() {
 		frappe.flags.dialog_set = false;
 		const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
@@ -1074,6 +1106,10 @@
 
 		if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
 		if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
+
+		if (cint(frappe.user_defaults?.use_serial_batch_fields)) {
+			frappe.model.set_value(row.doctype, row.name, "use_serial_batch_fields", 1);
+		}
 	}
 
 	from_warehouse(doc) {
@@ -1144,7 +1180,8 @@
 							if (r) {
 								frappe.model.set_value(item.doctype, item.name, {
 									"serial_and_batch_bundle": r.name,
-									"qty": Math.abs(r.total_qty)
+									"use_serial_batch_fields": 0,
+									"qty": Math.abs(r.total_qty) / flt(item.conversion_factor || 1, precision("conversion_factor", item))
 								});
 							}
 						}
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index faccfa3..399e698 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -274,6 +274,7 @@
 
 	def on_submit(self):
 		self.validate_closed_subcontracting_order()
+		self.make_bundle_using_old_serial_batch_fields()
 		self.update_stock_ledger()
 		self.update_work_order()
 		self.validate_subcontract_order()
@@ -838,6 +839,7 @@
 					currency=erpnext.get_company_currency(self.company),
 					company=self.company,
 					raise_error_if_no_rate=raise_error_if_no_rate,
+					batch_no=d.batch_no,
 					serial_and_batch_bundle=d.serial_and_batch_bundle,
 				)
 
@@ -866,7 +868,7 @@
 				if reset_outgoing_rate:
 					args = self.get_args_for_incoming_rate(d)
 					rate = get_incoming_rate(args, raise_error_if_no_rate)
-					if rate > 0:
+					if rate >= 0:
 						d.basic_rate = rate
 
 				d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
@@ -889,6 +891,8 @@
 				"allow_zero_valuation": item.allow_zero_valuation_rate,
 				"serial_and_batch_bundle": item.serial_and_batch_bundle,
 				"voucher_detail_no": item.name,
+				"batch_no": item.batch_no,
+				"serial_no": item.serial_no,
 			}
 		)
 
@@ -903,14 +907,62 @@
 				return flt(outgoing_items_cost / total_fg_qty)
 
 	def get_basic_rate_for_manufactured_item(self, finished_item_qty, outgoing_items_cost=0) -> float:
+		settings = frappe.get_single("Manufacturing Settings")
 		scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
 
-		# Get raw materials cost from BOM if multiple material consumption entries
-		if not outgoing_items_cost and frappe.db.get_single_value(
-			"Manufacturing Settings", "material_consumption", cache=True
-		):
-			bom_items = self.get_bom_raw_materials(finished_item_qty)
-			outgoing_items_cost = sum([flt(row.qty) * flt(row.rate) for row in bom_items.values()])
+		if settings.material_consumption:
+			if settings.get_rm_cost_from_consumption_entry and self.work_order:
+
+				# Validate only if Material Consumption Entry exists for the Work Order.
+				if frappe.db.exists(
+					"Stock Entry",
+					{
+						"docstatus": 1,
+						"work_order": self.work_order,
+						"purpose": "Material Consumption for Manufacture",
+					},
+				):
+					for item in self.items:
+						if not item.is_finished_item and not item.is_scrap_item:
+							label = frappe.get_meta(settings.doctype).get_label("get_rm_cost_from_consumption_entry")
+							frappe.throw(
+								_(
+									"Row {0}: As {1} is enabled, raw materials cannot be added to {2} entry. Use {3} entry to consume raw materials."
+								).format(
+									item.idx,
+									frappe.bold(label),
+									frappe.bold("Manufacture"),
+									frappe.bold("Material Consumption for Manufacture"),
+								)
+							)
+
+					if frappe.db.exists(
+						"Stock Entry", {"docstatus": 1, "work_order": self.work_order, "purpose": "Manufacture"}
+					):
+						frappe.throw(
+							_("Only one {0} entry can be created against the Work Order {1}").format(
+								frappe.bold("Manufacture"), frappe.bold(self.work_order)
+							)
+						)
+
+					SE = frappe.qb.DocType("Stock Entry")
+					SE_ITEM = frappe.qb.DocType("Stock Entry Detail")
+
+					outgoing_items_cost = (
+						frappe.qb.from_(SE)
+						.left_join(SE_ITEM)
+						.on(SE.name == SE_ITEM.parent)
+						.select(Sum(SE_ITEM.valuation_rate * SE_ITEM.transfer_qty))
+						.where(
+							(SE.docstatus == 1)
+							& (SE.work_order == self.work_order)
+							& (SE.purpose == "Material Consumption for Manufacture")
+						)
+					).run()[0][0] or 0
+
+			elif not outgoing_items_cost:
+				bom_items = self.get_bom_raw_materials(finished_item_qty)
+				outgoing_items_cost = sum([flt(row.qty) * flt(row.rate) for row in bom_items.values()])
 
 		return flt((outgoing_items_cost - scrap_items_cost) / finished_item_qty)
 
@@ -981,6 +1033,9 @@
 		already_picked_serial_nos = []
 
 		for row in self.items:
+			if row.use_serial_batch_fields:
+				continue
+
 			if not row.s_warehouse:
 				continue
 
@@ -988,7 +1043,7 @@
 				continue
 
 			bundle_doc = None
-			if row.serial_and_batch_bundle and abs(row.qty) != abs(
+			if row.serial_and_batch_bundle and abs(row.transfer_qty) != abs(
 				frappe.get_cached_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "total_qty")
 			):
 				bundle_doc = SerialBatchCreation(
@@ -998,7 +1053,7 @@
 						"serial_and_batch_bundle": row.serial_and_batch_bundle,
 						"type_of_transaction": "Outward",
 						"ignore_serial_nos": already_picked_serial_nos,
-						"qty": row.qty * -1,
+						"qty": row.transfer_qty * -1,
 					}
 				).update_serial_and_batch_entries()
 			elif not row.serial_and_batch_bundle:
@@ -1010,7 +1065,7 @@
 						"posting_time": self.posting_time,
 						"voucher_type": self.doctype,
 						"voucher_detail_no": row.name,
-						"qty": row.qty * -1,
+						"qty": row.transfer_qty * -1,
 						"ignore_serial_nos": already_picked_serial_nos,
 						"type_of_transaction": "Outward",
 						"company": self.company,
@@ -1847,6 +1902,7 @@
 			return
 
 		id = create_serial_and_batch_bundle(
+			self,
 			row,
 			frappe._dict(
 				{
@@ -2117,7 +2173,7 @@
 			"to_warehouse": "",
 			"qty": qty,
 			"item_name": item.item_name,
-			"serial_and_batch_bundle": create_serial_and_batch_bundle(row, item, "Outward"),
+			"serial_and_batch_bundle": create_serial_and_batch_bundle(self, row, item, "Outward"),
 			"description": item.description,
 			"stock_uom": item.stock_uom,
 			"expense_account": item.expense_account,
@@ -2495,6 +2551,7 @@
 					row = frappe._dict({"serial_nos": serial_nos[0 : cint(d.qty)]})
 
 					id = create_serial_and_batch_bundle(
+						self,
 						row,
 						frappe._dict(
 							{
@@ -3018,7 +3075,7 @@
 	return data
 
 
-def create_serial_and_batch_bundle(row, child, type_of_transaction=None):
+def create_serial_and_batch_bundle(parent_doc, row, child, type_of_transaction=None):
 	item_details = frappe.get_cached_value(
 		"Item", child.item_code, ["has_serial_no", "has_batch_no"], as_dict=1
 	)
@@ -3036,6 +3093,8 @@
 			"item_code": child.item_code,
 			"warehouse": child.warehouse,
 			"type_of_transaction": type_of_transaction,
+			"posting_date": parent_doc.posting_date,
+			"posting_time": parent_doc.posting_time,
 		}
 	)
 
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
index 83bfaa0..271cbbc 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
@@ -133,7 +133,7 @@
 	serial_number = args.serial_no
 
 	bundle_id = None
-	if args.serial_no or args.batch_no or args.batches:
+	if not args.use_serial_batch_fields and (args.serial_no or args.batch_no or args.batches):
 		batches = frappe._dict({})
 		if args.batch_no:
 			batches = frappe._dict({args.batch_no: args.qty})
@@ -161,7 +161,12 @@
 			.name
 		)
 
-	args.serial_no = serial_number
+		args["serial_no"] = ""
+		args["batch_no"] = ""
+
+	else:
+		args.serial_no = serial_number
+
 	s.append(
 		"items",
 		{
@@ -177,6 +182,7 @@
 			"batch_no": args.batch_no,
 			"cost_center": args.cost_center,
 			"expense_account": args.expense_account,
+			"use_serial_batch_fields": args.use_serial_batch_fields,
 		},
 	)
 
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 23dacc8..9d1a3f7 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -680,6 +680,7 @@
 	def test_serial_move(self):
 		se = make_serialized_item()
 		serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
+		frappe.flags.use_serial_and_batch_fields = True
 
 		se = frappe.copy_doc(test_records[0])
 		se.purpose = "Material Transfer"
@@ -700,6 +701,7 @@
 		self.assertTrue(
 			frappe.db.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse - _TC"
 		)
+		frappe.flags.use_serial_and_batch_fields = False
 
 	def test_serial_cancel(self):
 		se, serial_nos = self.test_serial_by_series()
@@ -999,6 +1001,8 @@
 			do_not_save=True,
 		)
 
+		frappe.flags.use_serial_and_batch_fields = True
+
 		cls_obj = SerialBatchCreation(
 			{
 				"type_of_transaction": "Inward",
@@ -1035,84 +1039,7 @@
 
 		s2.submit()
 		s2.cancel()
-
-	# def test_retain_sample(self):
-	# 	from erpnext.stock.doctype.batch.batch import get_batch_qty
-	# 	from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
-
-	# 	create_warehouse("Test Warehouse for Sample Retention")
-	# 	frappe.db.set_value(
-	# 		"Stock Settings",
-	# 		None,
-	# 		"sample_retention_warehouse",
-	# 		"Test Warehouse for Sample Retention - _TC",
-	# 	)
-
-	# 	test_item_code = "Retain Sample Item"
-	# 	if not frappe.db.exists("Item", test_item_code):
-	# 		item = frappe.new_doc("Item")
-	# 		item.item_code = test_item_code
-	# 		item.item_name = "Retain Sample Item"
-	# 		item.description = "Retain Sample Item"
-	# 		item.item_group = "All Item Groups"
-	# 		item.is_stock_item = 1
-	# 		item.has_batch_no = 1
-	# 		item.create_new_batch = 1
-	# 		item.retain_sample = 1
-	# 		item.sample_quantity = 4
-	# 		item.save()
-
-	# 	receipt_entry = frappe.new_doc("Stock Entry")
-	# 	receipt_entry.company = "_Test Company"
-	# 	receipt_entry.purpose = "Material Receipt"
-	# 	receipt_entry.append(
-	# 		"items",
-	# 		{
-	# 			"item_code": test_item_code,
-	# 			"t_warehouse": "_Test Warehouse - _TC",
-	# 			"qty": 40,
-	# 			"basic_rate": 12,
-	# 			"cost_center": "_Test Cost Center - _TC",
-	# 			"sample_quantity": 4,
-	# 		},
-	# 	)
-	# 	receipt_entry.set_stock_entry_type()
-	# 	receipt_entry.insert()
-	# 	receipt_entry.submit()
-
-	# 	retention_data = move_sample_to_retention_warehouse(
-	# 		receipt_entry.company, receipt_entry.get("items")
-	# 	)
-	# 	retention_entry = frappe.new_doc("Stock Entry")
-	# 	retention_entry.company = retention_data.company
-	# 	retention_entry.purpose = retention_data.purpose
-	# 	retention_entry.append(
-	# 		"items",
-	# 		{
-	# 			"item_code": test_item_code,
-	# 			"t_warehouse": "Test Warehouse for Sample Retention - _TC",
-	# 			"s_warehouse": "_Test Warehouse - _TC",
-	# 			"qty": 4,
-	# 			"basic_rate": 12,
-	# 			"cost_center": "_Test Cost Center - _TC",
-	# 			"batch_no": get_batch_from_bundle(receipt_entry.get("items")[0].serial_and_batch_bundle),
-	# 		},
-	# 	)
-	# 	retention_entry.set_stock_entry_type()
-	# 	retention_entry.insert()
-	# 	retention_entry.submit()
-
-	# 	qty_in_usable_warehouse = get_batch_qty(
-	# 		get_batch_from_bundle(receipt_entry.get("items")[0].serial_and_batch_bundle), "_Test Warehouse - _TC", "_Test Item"
-	# 	)
-	# 	qty_in_retention_warehouse = get_batch_qty(
-	# 		get_batch_from_bundle(receipt_entry.get("items")[0].serial_and_batch_bundle),
-	# 		"Test Warehouse for Sample Retention - _TC",
-	# 		"_Test Item",
-	# 	)
-
-	# 	self.assertEqual(qty_in_usable_warehouse, 36)
-	# 	self.assertEqual(qty_in_retention_warehouse, 4)
+		frappe.flags.use_serial_and_batch_fields = False
 
 	def test_quality_check(self):
 		item_code = "_Test Item For QC"
@@ -1669,6 +1596,7 @@
 			qty=4,
 			to_warehouse="_Test Warehouse - _TC",
 			batch_no=batch.name,
+			use_serial_batch_fields=1,
 			do_not_save=True,
 		)
 
@@ -1683,24 +1611,22 @@
 		item_code = "Test Negative Item - 001"
 		item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
 
-		make_stock_entry(
+		se1 = make_stock_entry(
 			item_code=item_code,
 			posting_date=add_days(today(), -3),
 			posting_time="00:00:00",
-			purpose="Material Receipt",
+			target="_Test Warehouse - _TC",
 			qty=10,
 			to_warehouse="_Test Warehouse - _TC",
-			do_not_save=True,
 		)
 
-		make_stock_entry(
+		se2 = make_stock_entry(
 			item_code=item_code,
 			posting_date=today(),
 			posting_time="00:00:00",
-			purpose="Material Receipt",
+			source="_Test Warehouse - _TC",
 			qty=8,
 			from_warehouse="_Test Warehouse - _TC",
-			do_not_save=True,
 		)
 
 		sr_doc = create_stock_reconciliation(
@@ -1785,6 +1711,93 @@
 
 		self.assertRaises(frappe.ValidationError, se1.cancel)
 
+	def test_auto_reorder_level(self):
+		from erpnext.stock.reorder_item import reorder_item
+
+		item_doc = make_item(
+			"Test Auto Reorder Item - 001",
+			properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1},
+			uoms=[{"uom": "Nos", "conversion_factor": 5}],
+		)
+
+		if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}):
+			item_doc.append(
+				"reorder_levels",
+				{
+					"warehouse_reorder_level": 0,
+					"warehouse_reorder_qty": 10,
+					"warehouse": "_Test Warehouse - _TC",
+					"material_request_type": "Purchase",
+				},
+			)
+
+		item_doc.save(ignore_permissions=True)
+
+		frappe.db.set_single_value("Stock Settings", "auto_indent", 1)
+
+		mr_list = reorder_item()
+
+		frappe.db.set_single_value("Stock Settings", "auto_indent", 0)
+		mrs = frappe.get_all(
+			"Material Request Item",
+			fields=["qty", "stock_uom", "stock_qty"],
+			filters={"item_code": item_doc.name, "uom": "Nos"},
+		)
+
+		for mri in mrs:
+			self.assertEqual(mri.stock_uom, "Kg")
+			self.assertEqual(mri.stock_qty, 10)
+			self.assertEqual(mri.qty, 2)
+
+		for mr in mr_list:
+			mr.cancel()
+			mr.delete()
+
+	def test_use_serial_and_batch_fields(self):
+		item = make_item(
+			"Test Use Serial and Batch Item SN Item",
+			{"has_serial_no": 1, "is_stock_item": 1},
+		)
+
+		serial_nos = [
+			"Test Use Serial and Batch Item SN Item - SN 001",
+			"Test Use Serial and Batch Item SN Item - SN 002",
+		]
+
+		se = make_stock_entry(
+			item_code=item.name,
+			qty=2,
+			to_warehouse="_Test Warehouse - _TC",
+			use_serial_batch_fields=1,
+			serial_no="\n".join(serial_nos),
+		)
+
+		self.assertTrue(se.items[0].use_serial_batch_fields)
+		self.assertTrue(se.items[0].serial_no)
+		self.assertTrue(se.items[0].serial_and_batch_bundle)
+
+		for serial_no in serial_nos:
+			self.assertTrue(frappe.db.exists("Serial No", serial_no))
+			self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Active")
+
+		se1 = make_stock_entry(
+			item_code=item.name,
+			qty=2,
+			from_warehouse="_Test Warehouse - _TC",
+			use_serial_batch_fields=1,
+			serial_no="\n".join(serial_nos),
+		)
+
+		se1.reload()
+
+		self.assertTrue(se1.items[0].use_serial_batch_fields)
+		self.assertTrue(se1.items[0].serial_no)
+		self.assertTrue(se1.items[0].serial_and_batch_bundle)
+
+		for serial_no in serial_nos:
+			self.assertTrue(frappe.db.exists("Serial No", serial_no))
+			self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered")
+
 
 def make_serialized_item(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index bd84a2b..48fc31a 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -47,9 +47,12 @@
   "amount",
   "serial_no_batch",
   "add_serial_batch_bundle",
-  "serial_and_batch_bundle",
+  "use_serial_batch_fields",
   "col_break4",
+  "serial_and_batch_bundle",
+  "section_break_rdtg",
   "serial_no",
+  "column_break_prps",
   "batch_no",
   "accounting",
   "expense_account",
@@ -289,27 +292,27 @@
    "no_copy": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "serial_no",
-   "fieldtype": "Small Text",
+   "fieldtype": "Text",
    "label": "Serial No",
    "no_copy": 1,
    "oldfieldname": "serial_no",
-   "oldfieldtype": "Text",
-   "read_only": 1
+   "oldfieldtype": "Text"
   },
   {
    "fieldname": "col_break4",
    "fieldtype": "Column Break"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "batch_no",
    "fieldtype": "Link",
    "label": "Batch No",
    "no_copy": 1,
    "oldfieldname": "batch_no",
    "oldfieldtype": "Link",
-   "options": "Batch",
-   "read_only": 1
+   "options": "Batch"
   },
   {
    "depends_on": "eval:parent.inspection_required && doc.t_warehouse",
@@ -573,24 +576,41 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0",
    "fieldname": "add_serial_batch_bundle",
    "fieldtype": "Button",
    "label": "Add Serial / Batch No"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
    "no_copy": 1,
    "options": "Serial and Batch Bundle",
    "print_hide": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
+   "fieldname": "section_break_rdtg",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_prps",
+   "fieldtype": "Column Break"
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2024-01-12 11:56:04.626103",
+ "modified": "2024-02-25 15:58:40.982582",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry Detail",
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py
index a6dd0fa..bd3dda1 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py
@@ -54,7 +54,7 @@
 		sample_quantity: DF.Int
 		sco_rm_detail: DF.Data | None
 		serial_and_batch_bundle: DF.Link | None
-		serial_no: DF.SmallText | None
+		serial_no: DF.Text | None
 		set_basic_rate_manually: DF.Check
 		ste_detail: DF.Data | None
 		stock_uom: DF.Link
@@ -63,6 +63,7 @@
 		transfer_qty: DF.Float
 		transferred_qty: DF.Float
 		uom: DF.Link
+		use_serial_batch_fields: DF.Check
 		valuation_rate: DF.Currency
 	# end: auto-generated types
 
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 be37994..3a094f1 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
@@ -11,6 +11,7 @@
   "warehouse",
   "posting_date",
   "posting_time",
+  "posting_datetime",
   "is_adjustment_entry",
   "auto_created_serial_and_batch_bundle",
   "column_break_6",
@@ -100,7 +101,6 @@
    "oldfieldtype": "Date",
    "print_width": "100px",
    "read_only": 1,
-   "search_index": 1,
    "width": "100px"
   },
   {
@@ -253,7 +253,6 @@
    "options": "Company",
    "print_width": "150px",
    "read_only": 1,
-   "search_index": 1,
    "width": "150px"
   },
   {
@@ -348,6 +347,11 @@
    "fieldname": "auto_created_serial_and_batch_bundle",
    "fieldtype": "Check",
    "label": "Auto Created Serial and Batch Bundle"
+  },
+  {
+   "fieldname": "posting_datetime",
+   "fieldtype": "Datetime",
+   "label": "Posting Datetime"
   }
  ],
  "hide_toolbar": 1,
@@ -356,7 +360,7 @@
  "in_create": 1,
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2023-11-14 16:47:39.791967",
+ "modified": "2024-02-07 09:18:13.999231",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Ledger Entry",
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 277ca01..a3e51ca 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -51,6 +51,7 @@
 		item_code: DF.Link | None
 		outgoing_rate: DF.Currency
 		posting_date: DF.Date | None
+		posting_datetime: DF.Datetime | None
 		posting_time: DF.Time | None
 		project: DF.Link | None
 		qty_after_transaction: DF.Float
@@ -92,7 +93,16 @@
 		self.validate_with_last_transaction_posting_time()
 		self.validate_inventory_dimension_negative_stock()
 
+	def set_posting_datetime(self):
+		from erpnext.stock.utils import get_combine_datetime
+
+		self.posting_datetime = get_combine_datetime(self.posting_date, self.posting_time)
+		self.db_set("posting_datetime", self.posting_datetime)
+
 	def validate_inventory_dimension_negative_stock(self):
+		if self.is_cancelled:
+			return
+
 		extra_cond = ""
 		kwargs = {}
 
@@ -159,6 +169,7 @@
 		return inv_dimension_dict
 
 	def on_submit(self):
+		self.set_posting_datetime()
 		self.check_stock_frozen_date()
 
 		# Added to handle few test cases where serial_and_batch_bundles are not required
@@ -329,9 +340,7 @@
 
 
 def on_doctype_update():
-	frappe.db.add_index(
-		"Stock Ledger Entry", fields=["posting_date", "posting_time"], index_name="posting_sort_index"
-	)
 	frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
 	frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
 	frappe.db.add_index("Stock Ledger Entry", ["warehouse", "item_code"], "item_warehouse")
+	frappe.db.add_index("Stock Ledger Entry", ["posting_datetime", "creation"])
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index d8a3f2e..40a2d5a 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -2,6 +2,7 @@
 # See license.txt
 
 import json
+import time
 from uuid import uuid4
 
 import frappe
@@ -482,6 +483,8 @@
 			(item, warehouses[0], batches[1], 1, 200),
 			(item, warehouses[0], batches[0], 1, 200),
 		]
+
+		frappe.flags.use_serial_and_batch_fields = True
 		dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list)
 		sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"])
 		svd_list = [-1 * d["stock_value_difference"] for d in sle_details]
@@ -494,6 +497,8 @@
 				"Incorrect 'Incoming Rate' values fetched for DN items",
 			)
 
+		frappe.flags.use_serial_and_batch_fields = False
+
 	def test_batchwise_item_valuation_stock_reco(self):
 		item, warehouses, batches = setup_item_valuation_test()
 		state = {"stock_value": 0.0, "qty": 0.0}
@@ -1073,7 +1078,7 @@
 				frappe.qb.from_(sle)
 				.select("qty_after_transaction")
 				.where((sle.item_code == item) & (sle.warehouse == warehouse) & (sle.is_cancelled == 0))
-				.orderby(CombineDatetime(sle.posting_date, sle.posting_time))
+				.orderby(sle.posting_datetime)
 				.orderby(sle.creation)
 			).run(pluck=True)
 
@@ -1150,6 +1155,89 @@
 		except Exception as e:
 			self.fail("Double processing of qty for clashing timestamp.")
 
+	def test_previous_sle_with_clashed_timestamp(self):
+
+		item = make_item().name
+		warehouse = "_Test Warehouse - _TC"
+
+		reciept1 = make_stock_entry(
+			item_code=item,
+			to_warehouse=warehouse,
+			qty=100,
+			rate=10,
+			posting_date="2021-01-01",
+			posting_time="02:00:00",
+		)
+
+		time.sleep(3)
+
+		reciept2 = make_stock_entry(
+			item_code=item,
+			to_warehouse=warehouse,
+			qty=5,
+			posting_date="2021-01-01",
+			rate=10,
+			posting_time="02:00:00.1234",
+		)
+
+		sle = frappe.get_all(
+			"Stock Ledger Entry",
+			filters={"voucher_no": reciept1.name},
+			fields=["qty_after_transaction", "actual_qty"],
+		)
+		self.assertEqual(sle[0].qty_after_transaction, 100)
+		self.assertEqual(sle[0].actual_qty, 100)
+
+		sle = frappe.get_all(
+			"Stock Ledger Entry",
+			filters={"voucher_no": reciept2.name},
+			fields=["qty_after_transaction", "actual_qty"],
+		)
+		self.assertEqual(sle[0].qty_after_transaction, 105)
+		self.assertEqual(sle[0].actual_qty, 5)
+
+	def test_backdated_sle_with_same_timestamp(self):
+
+		item = make_item().name
+		warehouse = "_Test Warehouse - _TC"
+
+		reciept1 = make_stock_entry(
+			item_code=item,
+			to_warehouse=warehouse,
+			qty=5,
+			posting_date="2021-01-01",
+			rate=10,
+			posting_time="02:00:00.1234",
+		)
+
+		time.sleep(3)
+
+		# backdated entry with same timestamp but different ms part
+		reciept2 = make_stock_entry(
+			item_code=item,
+			to_warehouse=warehouse,
+			qty=100,
+			rate=10,
+			posting_date="2021-01-01",
+			posting_time="02:00:00",
+		)
+
+		sle = frappe.get_all(
+			"Stock Ledger Entry",
+			filters={"voucher_no": reciept1.name},
+			fields=["qty_after_transaction", "actual_qty"],
+		)
+		self.assertEqual(sle[0].qty_after_transaction, 5)
+		self.assertEqual(sle[0].actual_qty, 5)
+
+		sle = frappe.get_all(
+			"Stock Ledger Entry",
+			filters={"voucher_no": reciept2.name},
+			fields=["qty_after_transaction", "actual_qty"],
+		)
+		self.assertEqual(sle[0].qty_after_transaction, 105)
+		self.assertEqual(sle[0].actual_qty, 100)
+
 	@change_settings("System Settings", {"float_precision": 3, "currency_precision": 2})
 	def test_transfer_invariants(self):
 		"""Extact stock value should be transferred."""
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 8e9dcb0..ba7f9c5 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -198,6 +198,7 @@
 					frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty);
 					frappe.model.set_value(cdt, cdn, "amount", row.qty * row.valuation_rate);
 					frappe.model.set_value(cdt, cdn, "current_serial_no", r.message.serial_nos);
+					frappe.model.set_value(cdt, cdn, "use_serial_batch_fields", r.message.use_serial_batch_fields);
 
 					if (frm.doc.purpose == "Stock Reconciliation" && !frm.doc.scan_mode) {
 						frappe.model.set_value(cdt, cdn, "serial_no", r.message.serial_nos);
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 788ae0d..06fd5f9 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -99,6 +99,8 @@
 					)
 
 	def on_submit(self):
+		self.make_bundle_for_current_qty()
+		self.make_bundle_using_old_serial_batch_fields()
 		self.update_stock_ledger()
 		self.make_gl_entries()
 		self.repost_future_sle_and_gle()
@@ -116,9 +118,52 @@
 		self.repost_future_sle_and_gle()
 		self.delete_auto_created_batches()
 
+	def make_bundle_for_current_qty(self):
+		from erpnext.stock.serial_batch_bundle import SerialBatchCreation
+
+		for row in self.items:
+			if not row.use_serial_batch_fields:
+				continue
+
+			if row.current_serial_and_batch_bundle:
+				continue
+
+			if row.current_qty and (row.current_serial_no or row.batch_no):
+				sn_doc = SerialBatchCreation(
+					{
+						"item_code": row.item_code,
+						"warehouse": row.warehouse,
+						"posting_date": self.posting_date,
+						"posting_time": self.posting_time,
+						"voucher_type": self.doctype,
+						"voucher_no": self.name,
+						"voucher_detail_no": row.name,
+						"qty": row.qty,
+						"type_of_transaction": "Outward",
+						"company": self.company,
+						"is_rejected": 0,
+						"serial_nos": get_serial_nos(row.current_serial_no) if row.current_serial_no else None,
+						"batches": frappe._dict({row.batch_no: row.qty}) if row.batch_no else None,
+						"batch_no": row.batch_no,
+						"do_not_submit": True,
+					}
+				).make_serial_and_batch_bundle()
+
+				row.current_serial_and_batch_bundle = sn_doc.name
+				row.db_set(
+					{
+						"current_serial_and_batch_bundle": sn_doc.name,
+						"current_serial_no": "",
+						"batch_no": "",
+					}
+				)
+
 	def set_current_serial_and_batch_bundle(self, voucher_detail_no=None, save=False) -> None:
 		"""Set Serial and Batch Bundle for each item"""
 		for item in self.items:
+			if not save and item.use_serial_batch_fields:
+				continue
+
 			if voucher_detail_no and voucher_detail_no != item.name:
 				continue
 
@@ -229,6 +274,9 @@
 
 	def set_new_serial_and_batch_bundle(self):
 		for item in self.items:
+			if item.use_serial_batch_fields:
+				continue
+
 			if not item.qty:
 				continue
 
@@ -291,8 +339,10 @@
 				inventory_dimensions_dict=inventory_dimensions_dict,
 			)
 
-			if (item.qty is None or item.qty == item_dict.get("qty")) and (
-				item.valuation_rate is None or item.valuation_rate == item_dict.get("rate")
+			if (
+				(item.qty is None or item.qty == item_dict.get("qty"))
+				and (item.valuation_rate is None or item.valuation_rate == item_dict.get("rate"))
+				and (not item.serial_no or (item.serial_no == item_dict.get("serial_nos")))
 			):
 				return False
 			else:
@@ -303,6 +353,11 @@
 				if item.valuation_rate is None:
 					item.valuation_rate = item_dict.get("rate")
 
+				if item_dict.get("serial_nos"):
+					item.current_serial_no = item_dict.get("serial_nos")
+					if self.purpose == "Stock Reconciliation" and not item.serial_no and item.qty:
+						item.serial_no = item.current_serial_no
+
 				item.current_qty = item_dict.get("qty")
 				item.current_valuation_rate = item_dict.get("rate")
 				self.calculate_difference_amount(item, item_dict)
@@ -779,6 +834,7 @@
 			if voucher_detail_no != row.name:
 				continue
 
+			val_rate = 0.0
 			current_qty = 0.0
 			if row.current_serial_and_batch_bundle:
 				current_qty = self.get_current_qty_for_serial_or_batch(row)
@@ -788,7 +844,6 @@
 					row.warehouse,
 					self.posting_date,
 					self.posting_time,
-					voucher_no=self.name,
 				)
 
 				current_qty = item_dict.get("qty")
@@ -830,7 +885,7 @@
 					{"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0},
 					"name",
 				)
-				and (not row.current_serial_and_batch_bundle and not row.batch_no)
+				and (not row.current_serial_and_batch_bundle)
 			):
 				self.set_current_serial_and_batch_bundle(voucher_detail_no, save=True)
 				row.reload()
@@ -851,8 +906,13 @@
 
 	def has_negative_stock_allowed(self):
 		allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
+		if allow_negative_stock:
+			return True
 
-		if all(d.serial_and_batch_bundle and flt(d.qty) == flt(d.current_qty) for d in self.items):
+		if any(
+			((d.serial_and_batch_bundle or d.batch_no) and flt(d.qty) == flt(d.current_qty))
+			for d in self.items
+		):
 			allow_negative_stock = True
 
 		return allow_negative_stock
@@ -1135,9 +1195,16 @@
 	has_serial_no = bool(item_dict.get("has_serial_no"))
 	has_batch_no = bool(item_dict.get("has_batch_no"))
 
+	use_serial_batch_fields = frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields")
+
 	if not batch_no and has_batch_no:
 		# Not enough information to fetch data
-		return {"qty": 0, "rate": 0, "serial_nos": None}
+		return {
+			"qty": 0,
+			"rate": 0,
+			"serial_nos": None,
+			"use_serial_batch_fields": use_serial_batch_fields,
+		}
 
 	# TODO: fetch only selected batch's values
 	data = get_stock_balance(
@@ -1160,7 +1227,12 @@
 			get_batch_qty(batch_no, warehouse, posting_date=posting_date, posting_time=posting_time) or 0
 		)
 
-	return {"qty": qty, "rate": rate, "serial_nos": serial_nos}
+	return {
+		"qty": qty,
+		"rate": rate,
+		"serial_nos": serial_nos,
+		"use_serial_batch_fields": use_serial_batch_fields,
+	}
 
 
 @frappe.whitelist()
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 0bbfed4..479a74a 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -1094,7 +1094,7 @@
 	)
 
 	bundle_id = None
-	if args.batch_no or args.serial_no:
+	if not args.use_serial_batch_fields and (args.batch_no or args.serial_no):
 		batches = frappe._dict({})
 		if args.batch_no:
 			batches[args.batch_no] = args.qty
@@ -1125,7 +1125,10 @@
 			"warehouse": args.warehouse or "_Test Warehouse - _TC",
 			"qty": args.qty,
 			"valuation_rate": args.rate,
+			"serial_no": args.serial_no if args.use_serial_batch_fields else None,
+			"batch_no": args.batch_no if args.use_serial_batch_fields else None,
 			"serial_and_batch_bundle": bundle_id,
+			"use_serial_batch_fields": args.use_serial_batch_fields,
 		},
 	)
 
diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
index fc4ae6a..7342259 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
@@ -19,11 +19,14 @@
   "allow_zero_valuation_rate",
   "serial_no_and_batch_section",
   "add_serial_batch_bundle",
-  "serial_and_batch_bundle",
-  "batch_no",
+  "use_serial_batch_fields",
   "column_break_11",
+  "serial_and_batch_bundle",
   "current_serial_and_batch_bundle",
+  "section_break_lypk",
   "serial_no",
+  "column_break_eefq",
+  "batch_no",
   "section_break_3",
   "current_qty",
   "current_amount",
@@ -103,10 +106,10 @@
    "label": "Serial No and Batch"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "serial_no",
    "fieldtype": "Long Text",
-   "label": "Serial No",
-   "read_only": 1
+   "label": "Serial No"
   },
   {
    "fieldname": "column_break_11",
@@ -171,11 +174,11 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "batch_no",
    "fieldtype": "Link",
    "label": "Batch No",
    "options": "Batch",
-   "read_only": 1,
    "search_index": 1
   },
   {
@@ -195,6 +198,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial / Batch Bundle",
@@ -204,6 +208,7 @@
    "search_index": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "current_serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Current Serial / Batch Bundle",
@@ -212,6 +217,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "add_serial_batch_bundle",
    "fieldtype": "Button",
    "label": "Add Serial / Batch No"
@@ -222,11 +228,26 @@
    "fieldtype": "Link",
    "label": "Item Group",
    "options": "Item Group"
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
+   "fieldname": "section_break_lypk",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_eefq",
+   "fieldtype": "Column Break"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2024-01-14 10:04:23.599951",
+ "modified": "2024-02-04 16:19:44.576022",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Reconciliation Item",
diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py
index c82cdf5..1938fec 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py
@@ -26,6 +26,7 @@
 		current_valuation_rate: DF.Currency
 		has_item_scanned: DF.Data | None
 		item_code: DF.Link
+		item_group: DF.Link | None
 		item_name: DF.Data | None
 		parent: DF.Data
 		parentfield: DF.Data
@@ -34,6 +35,7 @@
 		quantity_difference: DF.ReadOnly | None
 		serial_and_batch_bundle: DF.Link | None
 		serial_no: DF.LongText | None
+		use_serial_batch_fields: DF.Check
 		valuation_rate: DF.Currency
 		warehouse: DF.Link
 	# end: auto-generated types
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json
index 76cedd4..bf5ea74 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json
@@ -315,7 +315,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-10-19 16:41:16.545416",
+ "modified": "2024-02-07 16:05:17.772098",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Reservation Entry",
@@ -335,6 +335,90 @@
    "share": 1,
    "submit": 1,
    "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Sales Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Purchase Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Sales User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Purchase User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
   }
  ],
  "sort_field": "modified",
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
index 7e03ac3..26fe8e1 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
@@ -7,7 +7,7 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.query_builder.functions import Sum
-from frappe.utils import cint, flt
+from frappe.utils import cint, flt, nowdate, nowtime
 
 from erpnext.stock.utils import get_or_make_bin, get_stock_balance
 
@@ -866,6 +866,8 @@
 		bundle = frappe.new_doc("Serial and Batch Bundle")
 		bundle.type_of_transaction = "Outward"
 		bundle.voucher_type = "Delivery Note"
+		bundle.posting_date = nowdate()
+		bundle.posting_time = nowtime()
 
 		for field in ("item_code", "warehouse", "has_serial_no", "has_batch_no"):
 			setattr(bundle, field, sre[field])
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index dc27974..51036ad 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -50,6 +50,8 @@
   "disable_serial_no_and_batch_selector",
   "use_naming_series",
   "naming_series_prefix",
+  "use_serial_batch_fields",
+  "do_not_update_serial_batch_on_creation_of_auto_bundle",
   "stock_planning_tab",
   "auto_material_request",
   "auto_indent",
@@ -420,6 +422,21 @@
    "fieldname": "auto_reserve_stock_for_sales_order_on_purchase",
    "fieldtype": "Check",
    "label": "Auto Reserve Stock for Sales Order on Purchase"
+  },
+  {
+   "default": "1",
+   "description": "On submission of the stock transaction, system will auto create the Serial and Batch Bundle based on the Serial No / Batch fields.",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial / Batch Fields"
+  },
+  {
+   "default": "1",
+   "depends_on": "use_serial_batch_fields",
+   "description": "If enabled, do not update serial / batch values in the stock transactions on creation of auto Serial \n / Batch Bundle. ",
+   "fieldname": "do_not_update_serial_batch_on_creation_of_auto_bundle",
+   "fieldtype": "Check",
+   "label": "Do Not Update Serial / Batch on Creation of Auto Bundle"
   }
  ],
  "icon": "icon-cog",
@@ -427,7 +444,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2024-01-30 14:03:52.143457",
+ "modified": "2024-02-25 16:32:01.084453",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Settings",
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 088c7cd..d975c29 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -39,6 +39,7 @@
 		clean_description_html: DF.Check
 		default_warehouse: DF.Link | None
 		disable_serial_no_and_batch_selector: DF.Check
+		do_not_update_serial_batch_on_creation_of_auto_bundle: DF.Check
 		enable_stock_reservation: DF.Check
 		item_group: DF.Link | None
 		item_naming_by: DF.Literal["Item Code", "Naming Series"]
@@ -57,6 +58,7 @@
 		stock_uom: DF.Link | None
 		update_existing_price_list_rate: DF.Check
 		use_naming_series: DF.Check
+		use_serial_batch_fields: DF.Check
 		valuation_method: DF.Literal["FIFO", "Moving Average", "LIFO"]
 	# end: auto-generated types
 
@@ -68,6 +70,7 @@
 			"allow_negative_stock",
 			"default_warehouse",
 			"set_qty_in_transactions_based_on_serial_no_input",
+			"use_serial_batch_fields",
 		]:
 			frappe.db.set_default(key, self.get(key, ""))
 
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index 43b2ad2..7b0cade 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -13,6 +13,7 @@
   "column_break_3",
   "is_group",
   "parent_warehouse",
+  "is_rejected_warehouse",
   "column_break_4",
   "account",
   "company",
@@ -249,13 +250,20 @@
   {
    "fieldname": "column_break_qajx",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "description": "If yes, then this warehouse will be used to store rejected materials",
+   "fieldname": "is_rejected_warehouse",
+   "fieldtype": "Check",
+   "label": "Is Rejected Warehouse"
   }
  ],
  "icon": "fa fa-building",
  "idx": 1,
  "is_tree": 1,
  "links": [],
- "modified": "2023-05-29 13:10:43.333160",
+ "modified": "2024-01-24 16:27:28.299520",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Warehouse",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index bed5285..1cb1057 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -86,7 +86,8 @@
 
 	get_party_item_code(args, item, out)
 
-	set_valuation_rate(out, args)
+	if args.get("doctype") in ["Sales Order", "Quotation"]:
+		set_valuation_rate(out, args)
 
 	update_party_blanket_order(args, out)
 
@@ -269,7 +270,9 @@
 	if not item:
 		item = frappe.get_doc("Item", args.get("item_code"))
 
-	if item.variant_of and not item.taxes:
+	if (
+		item.variant_of and not item.taxes and frappe.db.exists("Item Tax", {"parent": item.variant_of})
+	):
 		item.update_template_tables()
 
 	item_defaults = get_item_defaults(item.name, args.company)
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 276531a..59f8b20 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -34,73 +34,157 @@
 		erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0]
 	)
 
-	items_to_consider = frappe.db.sql_list(
-		"""select name from `tabItem` item
-		where is_stock_item=1 and has_variants=0
-			and disabled=0
-			and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
-			and (exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
-				or (variant_of is not null and variant_of != ''
-				and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of))
-			)""",
-		{"today": nowdate()},
-	)
+	items_to_consider = get_items_for_reorder()
 
 	if not items_to_consider:
 		return
 
 	item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider)
 
-	def add_to_material_request(
-		item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None
-	):
-		if warehouse not in warehouse_company:
+	def add_to_material_request(**kwargs):
+		if isinstance(kwargs, dict):
+			kwargs = frappe._dict(kwargs)
+
+		if kwargs.warehouse not in warehouse_company:
 			# a disabled warehouse
 			return
 
-		reorder_level = flt(reorder_level)
-		reorder_qty = flt(reorder_qty)
+		reorder_level = flt(kwargs.reorder_level)
+		reorder_qty = flt(kwargs.reorder_qty)
 
 		# projected_qty will be 0 if Bin does not exist
-		if warehouse_group:
-			projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse_group))
+		if kwargs.warehouse_group:
+			projected_qty = flt(
+				item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse_group)
+			)
 		else:
-			projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse))
+			projected_qty = flt(
+				item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse)
+			)
 
 		if (reorder_level or reorder_qty) and projected_qty <= reorder_level:
 			deficiency = reorder_level - projected_qty
 			if deficiency > reorder_qty:
 				reorder_qty = deficiency
 
-			company = warehouse_company.get(warehouse) or default_company
+			company = warehouse_company.get(kwargs.warehouse) or default_company
 
-			material_requests[material_request_type].setdefault(company, []).append(
-				{"item_code": item_code, "warehouse": warehouse, "reorder_qty": reorder_qty}
+			material_requests[kwargs.material_request_type].setdefault(company, []).append(
+				{
+					"item_code": kwargs.item_code,
+					"warehouse": kwargs.warehouse,
+					"reorder_qty": reorder_qty,
+					"item_details": kwargs.item_details,
+				}
 			)
 
-	for item_code in items_to_consider:
-		item = frappe.get_doc("Item", item_code)
+	for item_code, reorder_levels in items_to_consider.items():
+		for d in reorder_levels:
+			if d.has_variants:
+				continue
 
-		if item.variant_of and not item.get("reorder_levels"):
-			item.update_template_tables()
-
-		if item.get("reorder_levels"):
-			for d in item.get("reorder_levels"):
-				add_to_material_request(
-					item_code,
-					d.warehouse,
-					d.warehouse_reorder_level,
-					d.warehouse_reorder_qty,
-					d.material_request_type,
-					warehouse_group=d.warehouse_group,
-				)
+			add_to_material_request(
+				item_code=item_code,
+				warehouse=d.warehouse,
+				reorder_level=d.warehouse_reorder_level,
+				reorder_qty=d.warehouse_reorder_qty,
+				material_request_type=d.material_request_type,
+				warehouse_group=d.warehouse_group,
+				item_details=frappe._dict(
+					{
+						"item_code": item_code,
+						"name": item_code,
+						"item_name": d.item_name,
+						"item_group": d.item_group,
+						"brand": d.brand,
+						"description": d.description,
+						"stock_uom": d.stock_uom,
+						"purchase_uom": d.purchase_uom,
+					}
+				),
+			)
 
 	if material_requests:
 		return create_material_request(material_requests)
 
 
+def get_items_for_reorder() -> dict[str, list]:
+	reorder_table = frappe.qb.DocType("Item Reorder")
+	item_table = frappe.qb.DocType("Item")
+
+	query = (
+		frappe.qb.from_(reorder_table)
+		.inner_join(item_table)
+		.on(reorder_table.parent == item_table.name)
+		.select(
+			reorder_table.warehouse,
+			reorder_table.warehouse_group,
+			reorder_table.material_request_type,
+			reorder_table.warehouse_reorder_level,
+			reorder_table.warehouse_reorder_qty,
+			item_table.name,
+			item_table.stock_uom,
+			item_table.purchase_uom,
+			item_table.description,
+			item_table.item_name,
+			item_table.item_group,
+			item_table.brand,
+			item_table.variant_of,
+			item_table.has_variants,
+		)
+		.where(
+			(item_table.disabled == 0)
+			& (item_table.is_stock_item == 1)
+			& (
+				(item_table.end_of_life.isnull())
+				| (item_table.end_of_life > nowdate())
+				| (item_table.end_of_life == "0000-00-00")
+			)
+		)
+	)
+
+	data = query.run(as_dict=True)
+	itemwise_reorder = frappe._dict({})
+	for d in data:
+		itemwise_reorder.setdefault(d.name, []).append(d)
+
+	itemwise_reorder = get_reorder_levels_for_variants(itemwise_reorder)
+
+	return itemwise_reorder
+
+
+def get_reorder_levels_for_variants(itemwise_reorder):
+	item_table = frappe.qb.DocType("Item")
+
+	query = (
+		frappe.qb.from_(item_table)
+		.select(
+			item_table.name,
+			item_table.variant_of,
+		)
+		.where(
+			(item_table.disabled == 0)
+			& (item_table.is_stock_item == 1)
+			& (
+				(item_table.end_of_life.isnull())
+				| (item_table.end_of_life > nowdate())
+				| (item_table.end_of_life == "0000-00-00")
+			)
+			& (item_table.variant_of.notnull())
+		)
+	)
+
+	variants_item = query.run(as_dict=True)
+	for row in variants_item:
+		if not itemwise_reorder.get(row.name) and itemwise_reorder.get(row.variant_of):
+			itemwise_reorder.setdefault(row.name, []).extend(itemwise_reorder.get(row.variant_of, []))
+
+	return itemwise_reorder
+
+
 def get_item_warehouse_projected_qty(items_to_consider):
 	item_warehouse_projected_qty = {}
+	items_to_consider = list(items_to_consider.keys())
 
 	for item_code, warehouse, projected_qty in frappe.db.sql(
 		"""select item_code, warehouse, projected_qty
@@ -164,7 +248,7 @@
 
 				for d in items:
 					d = frappe._dict(d)
-					item = frappe.get_doc("Item", d.item_code)
+					item = d.get("item_details")
 					uom = item.stock_uom
 					conversion_factor = 1.0
 
@@ -190,6 +274,7 @@
 							"item_code": d.item_code,
 							"schedule_date": add_days(nowdate(), cint(item.lead_time_days)),
 							"qty": qty,
+							"conversion_factor": conversion_factor,
 							"uom": uom,
 							"stock_uom": item.stock_uom,
 							"warehouse": d.warehouse,
diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py
index e4f657c..da958a8 100644
--- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py
+++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py
@@ -5,7 +5,7 @@
 import frappe
 from frappe import _
 from frappe.query_builder import Field
-from frappe.query_builder.functions import CombineDatetime, Min
+from frappe.query_builder.functions import Min
 from frappe.utils import add_days, getdate, today
 
 import erpnext
@@ -75,7 +75,7 @@
 			& (sle.company == report_filters.company)
 			& (sle.is_cancelled == 0)
 		)
-		.orderby(CombineDatetime(sle.posting_date, sle.posting_time), sle.creation)
+		.orderby(sle.posting_datetime, sle.creation)
 	).run(as_dict=True)
 
 	for d in data:
diff --git a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
index 9e75201..dd79e7f 100644
--- a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
+++ b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
@@ -213,13 +213,11 @@
 
 	query = (
 		frappe.qb.from_(sle)
-		.force_index("posting_sort_index")
 		.left_join(sle2)
 		.on(
 			(sle.item_code == sle2.item_code)
 			& (sle.warehouse == sle2.warehouse)
-			& (sle.posting_date < sle2.posting_date)
-			& (sle.posting_time < sle2.posting_time)
+			& (sle.posting_datetime < sle2.posting_datetime)
 			& (sle.name < sle2.name)
 		)
 		.select(sle.item_code, sle.warehouse, sle.qty_after_transaction, sle.company)
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index ed84a5c..500affa 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -8,7 +8,7 @@
 import frappe
 from frappe import _
 from frappe.query_builder import Order
-from frappe.query_builder.functions import Coalesce, CombineDatetime
+from frappe.query_builder.functions import Coalesce
 from frappe.utils import add_days, cint, date_diff, flt, getdate
 from frappe.utils.nestedset import get_descendants_of
 
@@ -90,8 +90,7 @@
 				self.opening_data.setdefault(group_by_key, entry)
 
 	def prepare_new_data(self):
-		if not self.sle_entries:
-			return
+		self.item_warehouse_map = self.get_item_warehouse_map()
 
 		if self.filters.get("show_stock_ageing_data"):
 			self.filters["show_warehouse_wise_stock"] = True
@@ -99,7 +98,8 @@
 
 		_func = itemgetter(1)
 
-		self.item_warehouse_map = self.get_item_warehouse_map()
+		del self.sle_entries
+
 		sre_details = self.get_sre_reserved_qty_details()
 
 		variant_values = {}
@@ -143,15 +143,22 @@
 		item_warehouse_map = {}
 		self.opening_vouchers = self.get_opening_vouchers()
 
-		for entry in self.sle_entries:
-			group_by_key = self.get_group_by_key(entry)
-			if group_by_key not in item_warehouse_map:
-				self.initialize_data(item_warehouse_map, group_by_key, entry)
+		if self.filters.get("show_stock_ageing_data"):
+			self.sle_entries = self.sle_query.run(as_dict=True)
 
-			self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key)
+		with frappe.db.unbuffered_cursor():
+			if not self.filters.get("show_stock_ageing_data"):
+				self.sle_entries = self.sle_query.run(as_dict=True, as_iterator=True)
 
-			if self.opening_data.get(group_by_key):
-				del self.opening_data[group_by_key]
+			for entry in self.sle_entries:
+				group_by_key = self.get_group_by_key(entry)
+				if group_by_key not in item_warehouse_map:
+					self.initialize_data(item_warehouse_map, group_by_key, entry)
+
+				self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key)
+
+				if self.opening_data.get(group_by_key):
+					del self.opening_data[group_by_key]
 
 		for group_by_key, entry in self.opening_data.items():
 			if group_by_key not in item_warehouse_map:
@@ -252,7 +259,8 @@
 			.where(
 				(table.docstatus == 1)
 				& (table.company == self.filters.company)
-				& ((table.to_date <= self.from_date))
+				& (table.to_date <= self.from_date)
+				& (table.status == "Completed")
 			)
 			.orderby(table.to_date, order=Order.desc)
 			.limit(1)
@@ -292,7 +300,7 @@
 				item_table.item_name,
 			)
 			.where((sle.docstatus < 2) & (sle.is_cancelled == 0))
-			.orderby(CombineDatetime(sle.posting_date, sle.posting_time))
+			.orderby(sle.posting_datetime)
 			.orderby(sle.creation)
 			.orderby(sle.actual_qty)
 		)
@@ -305,7 +313,7 @@
 		if self.filters.get("company"):
 			query = query.where(sle.company == self.filters.get("company"))
 
-		self.sle_entries = query.run(as_dict=True)
+		self.sle_query = query
 
 	def apply_inventory_dimensions_filters(self, query, sle) -> str:
 		inventory_dimension_fields = self.get_inventory_dimension_fields()
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index b00b422..2ec757b 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -91,6 +91,12 @@
 			"options": "Currency\nFloat",
 			"default": "Currency"
 		},
+		{
+			"fieldname": "segregate_serial_batch_bundle",
+			"label": __("Segregate Serial / Batch Bundle"),
+			"fieldtype": "Check",
+			"default": 0
+		}
 	],
 	"formatter": function (value, row, column, data, default_formatter) {
 		value = default_formatter(value, row, column, data);
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index e59f2fe..d859f4e 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -2,6 +2,8 @@
 # License: GNU General Public License v3. See license.txt
 
 
+import copy
+
 import frappe
 from frappe import _
 from frappe.query_builder.functions import CombineDatetime
@@ -26,6 +28,10 @@
 	item_details = get_item_details(items, sl_entries, include_uom)
 	opening_row = get_opening_balance(filters, columns, sl_entries)
 	precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
+	bundle_details = {}
+
+	if filters.get("segregate_serial_batch_bundle"):
+		bundle_details = get_serial_batch_bundle_details(sl_entries)
 
 	data = []
 	conversion_factors = []
@@ -45,6 +51,9 @@
 		item_detail = item_details[sle.item_code]
 
 		sle.update(item_detail)
+		if bundle_info := bundle_details.get(sle.serial_and_batch_bundle):
+			data.extend(get_segregated_bundle_entries(sle, bundle_info))
+			continue
 
 		if filters.get("batch_no") or inventory_dimension_filters_applied:
 			actual_qty += flt(sle.actual_qty, precision)
@@ -76,6 +85,60 @@
 	return columns, data
 
 
+def get_segregated_bundle_entries(sle, bundle_details):
+	segregated_entries = []
+	qty_before_transaction = sle.qty_after_transaction - sle.actual_qty
+	stock_value_before_transaction = sle.stock_value - sle.stock_value_difference
+
+	for row in bundle_details:
+		new_sle = copy.deepcopy(sle)
+		new_sle.update(row)
+
+		new_sle.update(
+			{
+				"in_out_rate": flt(new_sle.stock_value_difference / row.qty) if row.qty else 0,
+				"in_qty": row.qty if row.qty > 0 else 0,
+				"out_qty": row.qty if row.qty < 0 else 0,
+				"qty_after_transaction": qty_before_transaction + row.qty,
+				"stock_value": stock_value_before_transaction + new_sle.stock_value_difference,
+				"incoming_rate": row.incoming_rate if row.qty > 0 else 0,
+			}
+		)
+
+		qty_before_transaction += row.qty
+		stock_value_before_transaction += new_sle.stock_value_difference
+
+		new_sle.valuation_rate = (
+			stock_value_before_transaction / qty_before_transaction if qty_before_transaction else 0
+		)
+
+		segregated_entries.append(new_sle)
+
+	return segregated_entries
+
+
+def get_serial_batch_bundle_details(sl_entries):
+	bundle_details = []
+	for sle in sl_entries:
+		if sle.serial_and_batch_bundle:
+			bundle_details.append(sle.serial_and_batch_bundle)
+
+	if not bundle_details:
+		return frappe._dict({})
+
+	_bundle_details = frappe._dict({})
+	batch_entries = frappe.get_all(
+		"Serial and Batch Entry",
+		filters={"parent": ("in", bundle_details)},
+		fields=["parent", "qty", "incoming_rate", "stock_value_difference", "batch_no", "serial_no"],
+		order_by="parent, idx",
+	)
+	for entry in batch_entries:
+		_bundle_details.setdefault(entry.parent, []).append(entry)
+
+	return _bundle_details
+
+
 def update_available_serial_nos(available_serial_nos, sle):
 	serial_nos = get_serial_nos(sle.serial_no)
 	key = (sle.item_code, sle.warehouse)
@@ -256,7 +319,6 @@
 				"options": "Serial and Batch Bundle",
 				"width": 100,
 			},
-			{"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100},
 			{
 				"label": _("Project"),
 				"fieldname": "project",
@@ -283,7 +345,7 @@
 		frappe.qb.from_(sle)
 		.select(
 			sle.item_code,
-			CombineDatetime(sle.posting_date, sle.posting_time).as_("date"),
+			sle.posting_datetime.as_("date"),
 			sle.warehouse,
 			sle.posting_date,
 			sle.posting_time,
@@ -320,15 +382,45 @@
 	if items:
 		query = query.where(sle.item_code.isin(items))
 
-	for field in ["voucher_no", "batch_no", "project", "company"]:
+	for field in ["voucher_no", "project", "company"]:
 		if filters.get(field) and field not in inventory_dimension_fields:
 			query = query.where(sle[field] == filters.get(field))
 
+	if filters.get("batch_no"):
+		bundles = get_serial_and_batch_bundles(filters)
+
+		if bundles:
+			query = query.where(
+				(sle.serial_and_batch_bundle.isin(bundles)) | (sle.batch_no == filters.batch_no)
+			)
+		else:
+			query = query.where(sle.batch_no == filters.batch_no)
+
 	query = apply_warehouse_filter(query, sle, filters)
 
 	return query.run(as_dict=True)
 
 
+def get_serial_and_batch_bundles(filters):
+	SBB = frappe.qb.DocType("Serial and Batch Bundle")
+	SBE = frappe.qb.DocType("Serial and Batch Entry")
+
+	query = (
+		frappe.qb.from_(SBE)
+		.inner_join(SBB)
+		.on(SBE.parent == SBB.name)
+		.select(SBE.parent)
+		.where(
+			(SBB.docstatus == 1)
+			& (SBB.has_batch_no == 1)
+			& (SBB.voucher_no.notnull())
+			& (SBE.batch_no == filters.batch_no)
+		)
+	)
+
+	return query.run(pluck=SBE.parent)
+
+
 def get_inventory_dimension_fields():
 	return [dimension.fieldname for dimension in get_inventory_dimensions()]
 
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 4cfe5d8..24dd9d1 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -5,7 +5,7 @@
 from frappe import _, bold
 from frappe.model.naming import make_autoname
 from frappe.query_builder.functions import CombineDatetime, Sum
-from frappe.utils import cint, flt, get_link_to_form, now, nowtime, today
+from frappe.utils import cint, cstr, flt, get_link_to_form, now, nowtime, today
 
 from erpnext.stock.deprecated_serial_batch import (
 	DeprecatedBatchNoValuation,
@@ -138,9 +138,19 @@
 				self.child_doctype, self.sle.voucher_detail_no, "rejected_serial_and_batch_bundle", sn_doc.name
 			)
 		else:
-			frappe.db.set_value(
-				self.child_doctype, self.sle.voucher_detail_no, "serial_and_batch_bundle", sn_doc.name
-			)
+			values_to_update = {
+				"serial_and_batch_bundle": sn_doc.name,
+			}
+
+			if not frappe.db.get_single_value(
+				"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle"
+			):
+				if sn_doc.has_serial_no:
+					values_to_update["serial_no"] = ",".join(cstr(d.serial_no) for d in sn_doc.entries)
+				elif sn_doc.has_batch_no and len(sn_doc.entries) == 1:
+					values_to_update["batch_no"] = sn_doc.entries[0].batch_no
+
+			frappe.db.set_value(self.child_doctype, self.sle.voucher_detail_no, values_to_update)
 
 	@property
 	def child_doctype(self):
@@ -283,6 +293,7 @@
 				if (sn_table.purchase_document_no != self.sle.voucher_no and self.sle.is_cancelled != 1)
 				else "Inactive",
 			)
+			.set(sn_table.company, self.sle.company)
 			.where(sn_table.name.isin(serial_nos))
 		).run()
 
@@ -793,6 +804,9 @@
 			setattr(self, "actual_qty", qty)
 			self.__dict__["actual_qty"] = self.actual_qty
 
+		if not hasattr(self, "use_serial_batch_fields"):
+			setattr(self, "use_serial_batch_fields", 0)
+
 	def duplicate_package(self):
 		if not self.serial_and_batch_bundle:
 			return
@@ -904,6 +918,9 @@
 		if (self.get("batches") and self.has_batch_no) or (
 			self.get("serial_nos") and self.has_serial_no
 		):
+			if self.use_serial_batch_fields and self.get("serial_nos"):
+				self.make_serial_no_if_not_exists()
+
 			return
 
 		self.batch_no = None
@@ -915,6 +932,59 @@
 		else:
 			self.batches = frappe._dict({self.batch_no: abs(self.actual_qty)})
 
+	def make_serial_no_if_not_exists(self):
+		non_exists_serial_nos = []
+		for row in self.serial_nos:
+			if not frappe.db.exists("Serial No", row):
+				non_exists_serial_nos.append(row)
+
+		if non_exists_serial_nos:
+			self.make_serial_nos(non_exists_serial_nos)
+
+	def make_serial_nos(self, serial_nos):
+		serial_nos_details = []
+		batch_no = None
+		if self.batches:
+			batch_no = list(self.batches.keys())[0]
+
+		for serial_no in serial_nos:
+			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_name,
+					self.description,
+					"Active",
+					batch_no,
+				)
+			)
+
+		if serial_nos_details:
+			fields = [
+				"name",
+				"serial_no",
+				"creation",
+				"modified",
+				"owner",
+				"modified_by",
+				"warehouse",
+				"company",
+				"item_code",
+				"item_name",
+				"description",
+				"status",
+				"batch_no",
+			]
+
+			frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
+
 	def set_serial_batch_entries(self, doc):
 		if self.get("serial_nos"):
 			serial_no_wise_batch = frappe._dict({})
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index e88b192..2ae6c19 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -9,7 +9,7 @@
 import frappe
 from frappe import _, scrub
 from frappe.model.meta import get_field_precision
-from frappe.query_builder.functions import CombineDatetime, Sum
+from frappe.query_builder.functions import Sum
 from frappe.utils import (
 	cint,
 	cstr,
@@ -33,6 +33,7 @@
 	get_sre_reserved_serial_nos_details,
 )
 from erpnext.stock.utils import (
+	get_combine_datetime,
 	get_incoming_outgoing_rate_for_cancel,
 	get_incoming_rate,
 	get_or_make_bin,
@@ -95,6 +96,7 @@
 				sle_doc = make_entry(sle, allow_negative_stock, via_landed_cost_voucher)
 
 			args = sle_doc.as_dict()
+			args["posting_datetime"] = get_combine_datetime(args.posting_date, args.posting_time)
 
 			if sle.get("voucher_type") == "Stock Reconciliation":
 				# preserve previous_qty_after_transaction for qty reposting
@@ -616,12 +618,14 @@
 			self.process_sle(sle)
 
 	def get_sle_against_current_voucher(self):
-		self.args["time_format"] = "%H:%i:%s"
+		self.args["posting_datetime"] = get_combine_datetime(
+			self.args.posting_date, self.args.posting_time
+		)
 
 		return frappe.db.sql(
 			"""
 			select
-				*, timestamp(posting_date, posting_time) as "timestamp"
+				*, posting_datetime as "timestamp"
 			from
 				`tabStock Ledger Entry`
 			where
@@ -629,8 +633,7 @@
 				and warehouse = %(warehouse)s
 				and is_cancelled = 0
 				and (
-					posting_date = %(posting_date)s and
-					time_format(posting_time, %(time_format)s) = time_format(%(posting_time)s, %(time_format)s)
+					posting_datetime = %(posting_datetime)s
 				)
 			order by
 				creation ASC
@@ -899,7 +902,7 @@
 
 		precision = doc.precision("total_qty")
 		self.wh_data.qty_after_transaction += flt(doc.total_qty, precision)
-		if self.wh_data.qty_after_transaction:
+		if flt(self.wh_data.qty_after_transaction, precision):
 			self.wh_data.valuation_rate = flt(self.wh_data.stock_value, precision) / flt(
 				self.wh_data.qty_after_transaction, precision
 			)
@@ -1399,11 +1402,11 @@
 def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_voucher=False):
 	"""get stock ledger entries filtered by specific posting datetime conditions"""
 
-	args["time_format"] = "%H:%i:%s"
 	if not args.get("posting_date"):
-		args["posting_date"] = "1900-01-01"
-	if not args.get("posting_time"):
-		args["posting_time"] = "00:00"
+		args["posting_datetime"] = "1900-01-01 00:00:00"
+
+	if not args.get("posting_datetime"):
+		args["posting_datetime"] = get_combine_datetime(args["posting_date"], args["posting_time"])
 
 	voucher_condition = ""
 	if exclude_current_voucher:
@@ -1412,23 +1415,20 @@
 
 	sle = frappe.db.sql(
 		"""
-		select *, timestamp(posting_date, posting_time) as "timestamp"
+		select *, posting_datetime as "timestamp"
 		from `tabStock Ledger Entry`
 		where item_code = %(item_code)s
 			and warehouse = %(warehouse)s
 			and is_cancelled = 0
 			{voucher_condition}
 			and (
-				posting_date < %(posting_date)s or
-				(
-					posting_date = %(posting_date)s and
-					time_format(posting_time, %(time_format)s) {operator} time_format(%(posting_time)s, %(time_format)s)
-				)
+				posting_datetime {operator} %(posting_datetime)s
 			)
-		order by timestamp(posting_date, posting_time) desc, creation desc
+		order by posting_datetime desc, creation desc
 		limit 1
 		for update""".format(
-			operator=operator, voucher_condition=voucher_condition
+			operator=operator,
+			voucher_condition=voucher_condition,
 		),
 		args,
 		as_dict=1,
@@ -1469,9 +1469,7 @@
 	extra_cond=None,
 ):
 	"""get stock ledger entries filtered by specific posting datetime conditions"""
-	conditions = " and timestamp(posting_date, posting_time) {0} timestamp(%(posting_date)s, %(posting_time)s)".format(
-		operator
-	)
+	conditions = " and posting_datetime {0} %(posting_datetime)s".format(operator)
 	if previous_sle.get("warehouse"):
 		conditions += " and warehouse = %(warehouse)s"
 	elif previous_sle.get("warehouse_condition"):
@@ -1497,9 +1495,11 @@
 		)
 
 	if not previous_sle.get("posting_date"):
-		previous_sle["posting_date"] = "1900-01-01"
-	if not previous_sle.get("posting_time"):
-		previous_sle["posting_time"] = "00:00"
+		previous_sle["posting_datetime"] = "1900-01-01 00:00:00"
+	else:
+		previous_sle["posting_datetime"] = get_combine_datetime(
+			previous_sle["posting_date"], previous_sle["posting_time"]
+		)
 
 	if operator in (">", "<=") and previous_sle.get("name"):
 		conditions += " and name!=%(name)s"
@@ -1509,12 +1509,12 @@
 
 	return frappe.db.sql(
 		"""
-		select *, timestamp(posting_date, posting_time) as "timestamp"
+		select *, posting_datetime as "timestamp"
 		from `tabStock Ledger Entry`
 		where item_code = %%(item_code)s
 		and is_cancelled = 0
 		%(conditions)s
-		order by timestamp(posting_date, posting_time) %(order)s, creation %(order)s
+		order by posting_datetime %(order)s, creation %(order)s
 		%(limit)s %(for_update)s"""
 		% {
 			"conditions": conditions,
@@ -1540,7 +1540,7 @@
 			"posting_date",
 			"posting_time",
 			"voucher_detail_no",
-			"timestamp(posting_date, posting_time) as timestamp",
+			"posting_datetime as timestamp",
 		],
 		as_dict=1,
 	)
@@ -1552,13 +1552,10 @@
 
 	sle = frappe.qb.DocType("Stock Ledger Entry")
 
-	timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
-		posting_date, posting_time
-	)
+	timestamp_condition = sle.posting_datetime < get_combine_datetime(posting_date, posting_time)
 	if creation:
 		timestamp_condition |= (
-			CombineDatetime(sle.posting_date, sle.posting_time)
-			== CombineDatetime(posting_date, posting_time)
+			sle.posting_datetime == get_combine_datetime(posting_date, posting_time)
 		) & (sle.creation < creation)
 
 	batch_details = (
@@ -1639,7 +1636,7 @@
 			AND valuation_rate >= 0
 			AND is_cancelled = 0
 			AND NOT (voucher_no = %s AND voucher_type = %s)
-		order by posting_date desc, posting_time desc, name desc limit 1""",
+		order by posting_datetime desc, name desc limit 1""",
 		(item_code, warehouse, voucher_no, voucher_type),
 	):
 		return flt(last_valuation_rate[0][0])
@@ -1698,7 +1695,7 @@
 	datetime_limit_condition = ""
 	qty_shift = args.actual_qty
 
-	args["time_format"] = "%H:%i:%s"
+	args["posting_datetime"] = get_combine_datetime(args["posting_date"], args["posting_time"])
 
 	# find difference/shift in qty caused by stock reconciliation
 	if args.voucher_type == "Stock Reconciliation":
@@ -1708,8 +1705,6 @@
 	next_stock_reco_detail = get_next_stock_reco(args)
 	if next_stock_reco_detail:
 		detail = next_stock_reco_detail[0]
-
-		# add condition to update SLEs before this date & time
 		datetime_limit_condition = get_datetime_limit_condition(detail)
 
 	frappe.db.sql(
@@ -1722,13 +1717,9 @@
 			and voucher_no != %(voucher_no)s
 			and is_cancelled = 0
 			and (
-				posting_date > %(posting_date)s or
-				(
-					posting_date = %(posting_date)s and
-					time_format(posting_time, %(time_format)s) > time_format(%(posting_time)s, %(time_format)s)
-				)
+				posting_datetime > %(posting_datetime)s
 			)
-		{datetime_limit_condition}
+			{datetime_limit_condition}
 		""",
 		args,
 	)
@@ -1785,20 +1776,11 @@
 			& (sle.voucher_no != kwargs.get("voucher_no"))
 			& (sle.is_cancelled == 0)
 			& (
-				(
-					CombineDatetime(sle.posting_date, sle.posting_time)
-					> CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
-				)
-				| (
-					(
-						CombineDatetime(sle.posting_date, sle.posting_time)
-						== CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
-					)
-					& (sle.creation > kwargs.get("creation"))
-				)
+				sle.posting_datetime
+				>= get_combine_datetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
 			)
 		)
-		.orderby(CombineDatetime(sle.posting_date, sle.posting_time))
+		.orderby(sle.posting_datetime)
 		.orderby(sle.creation)
 		.limit(1)
 	)
@@ -1810,11 +1792,13 @@
 
 
 def get_datetime_limit_condition(detail):
+	posting_datetime = get_combine_datetime(detail.posting_date, detail.posting_time)
+
 	return f"""
 		and
-		(timestamp(posting_date, posting_time) < timestamp('{detail.posting_date}', '{detail.posting_time}')
+		(posting_datetime < '{posting_datetime}'
 			or (
-				timestamp(posting_date, posting_time) = timestamp('{detail.posting_date}', '{detail.posting_time}')
+				posting_datetime = '{posting_datetime}'
 				and creation < '{detail.creation}'
 			)
 		)"""
@@ -1888,10 +1872,10 @@
 			item_code = %(item_code)s
 			and warehouse = %(warehouse)s
 			and voucher_no != %(voucher_no)s
-			and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
+			and posting_datetime >= %(posting_datetime)s
 			and is_cancelled = 0
 			and qty_after_transaction < 0
-		order by timestamp(posting_date, posting_time) asc
+		order by posting_datetime asc
 		limit 1
 	""",
 		args,
@@ -1904,20 +1888,20 @@
 		"""
 		with batch_ledger as (
 			select
-				posting_date, posting_time, voucher_type, voucher_no,
-				sum(actual_qty) over (order by posting_date, posting_time, creation) as cumulative_total
+				posting_date, posting_time, posting_datetime, voucher_type, voucher_no,
+				sum(actual_qty) over (order by posting_datetime, creation) as cumulative_total
 			from `tabStock Ledger Entry`
 			where
 				item_code = %(item_code)s
 				and warehouse = %(warehouse)s
 				and batch_no=%(batch_no)s
 				and is_cancelled = 0
-			order by posting_date, posting_time, creation
+			order by posting_datetime, creation
 		)
 		select * from batch_ledger
 		where
 			cumulative_total < 0.0
-			and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
+			and posting_datetime >= %(posting_datetime)s
 		limit 1
 	""",
 		args,
@@ -2059,6 +2043,7 @@
 
 def get_stock_value_difference(item_code, warehouse, posting_date, posting_time, voucher_no=None):
 	table = frappe.qb.DocType("Stock Ledger Entry")
+	posting_datetime = get_combine_datetime(posting_date, posting_time)
 
 	query = (
 		frappe.qb.from_(table)
@@ -2067,10 +2052,7 @@
 			(table.is_cancelled == 0)
 			& (table.item_code == item_code)
 			& (table.warehouse == warehouse)
-			& (
-				(table.posting_date < posting_date)
-				| ((table.posting_date == posting_date) & (table.posting_time <= posting_time))
-			)
+			& (table.posting_datetime <= posting_datetime)
 		)
 	)
 
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 76af5d7..93e2fa4 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -8,9 +8,12 @@
 import frappe
 from frappe import _
 from frappe.query_builder.functions import CombineDatetime, IfNull, Sum
-from frappe.utils import cstr, flt, get_link_to_form, nowdate, nowtime
+from frappe.utils import cstr, flt, get_link_to_form, get_time, getdate, nowdate, nowtime
 
 import erpnext
+from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
+	get_available_serial_nos,
+)
 from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
 from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
 from erpnext.stock.valuation import FIFOValuation, LIFOValuation
@@ -125,7 +128,21 @@
 
 	if with_valuation_rate:
 		if with_serial_no:
-			serial_nos = get_serial_nos_data_after_transactions(args)
+			serial_no_details = get_available_serial_nos(
+				frappe._dict(
+					{
+						"item_code": item_code,
+						"warehouse": warehouse,
+						"posting_date": posting_date,
+						"posting_time": posting_time,
+						"ignore_warehouse": 1,
+					}
+				)
+			)
+
+			serial_nos = ""
+			if serial_no_details:
+				serial_nos = "\n".join(d.serial_no for d in serial_no_details)
 
 			return (
 				(last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
@@ -140,38 +157,6 @@
 		return last_entry.qty_after_transaction if last_entry else 0.0
 
 
-def get_serial_nos_data_after_transactions(args):
-
-	serial_nos = set()
-	args = frappe._dict(args)
-	sle = frappe.qb.DocType("Stock Ledger Entry")
-
-	stock_ledger_entries = (
-		frappe.qb.from_(sle)
-		.select("serial_no", "actual_qty")
-		.where(
-			(sle.item_code == args.item_code)
-			& (sle.warehouse == args.warehouse)
-			& (
-				CombineDatetime(sle.posting_date, sle.posting_time)
-				< CombineDatetime(args.posting_date, args.posting_time)
-			)
-			& (sle.is_cancelled == 0)
-		)
-		.orderby(sle.posting_date, sle.posting_time, sle.creation)
-		.run(as_dict=1)
-	)
-
-	for stock_ledger_entry in stock_ledger_entries:
-		changed_serial_no = get_serial_nos_data(stock_ledger_entry.serial_no)
-		if stock_ledger_entry.actual_qty > 0:
-			serial_nos.update(changed_serial_no)
-		else:
-			serial_nos.difference_update(changed_serial_no)
-
-	return "\n".join(serial_nos)
-
-
 def get_serial_nos_data(serial_nos):
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
@@ -277,7 +262,7 @@
 			item_code=args.get("item_code"),
 		)
 
-		in_rate = sn_obj.get_incoming_rate()
+		return sn_obj.get_incoming_rate()
 
 	elif item_details and item_details.has_batch_no and args.get("serial_and_batch_bundle"):
 		args.actual_qty = args.qty
@@ -287,23 +272,33 @@
 			item_code=args.get("item_code"),
 		)
 
-		in_rate = batch_obj.get_incoming_rate()
+		return batch_obj.get_incoming_rate()
 
 	elif (args.get("serial_no") or "").strip() and not args.get("serial_and_batch_bundle"):
-		in_rate = get_avg_purchase_rate(args.get("serial_no"))
+		args.actual_qty = args.qty
+		args.serial_nos = get_serial_nos_data(args.get("serial_no"))
+
+		sn_obj = SerialNoValuation(
+			sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code")
+		)
+
+		return sn_obj.get_incoming_rate()
 	elif (
 		args.get("batch_no")
 		and frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True)
 		and not args.get("serial_and_batch_bundle")
 	):
-		in_rate = get_batch_incoming_rate(
-			item_code=args.get("item_code"),
+
+		args.actual_qty = args.qty
+		args.batch_nos = frappe._dict({args.batch_no: args})
+
+		batch_obj = BatchNoValuation(
+			sle=args,
 			warehouse=args.get("warehouse"),
-			batch_no=args.get("batch_no"),
-			posting_date=args.get("posting_date"),
-			posting_time=args.get("posting_time"),
+			item_code=args.get("item_code"),
 		)
 
+		return batch_obj.get_incoming_rate()
 	else:
 		valuation_method = get_valuation_method(args.get("item_code"))
 		previous_sle = get_previous_sle(args)
@@ -662,3 +657,18 @@
 		):
 			scan_result.update(item_info)
 	return scan_result
+
+
+def get_combine_datetime(posting_date, posting_time):
+	import datetime
+
+	if isinstance(posting_date, str):
+		posting_date = getdate(posting_date)
+
+	if isinstance(posting_time, str):
+		posting_time = get_time(posting_time)
+
+	if isinstance(posting_time, datetime.timedelta):
+		posting_time = (datetime.datetime.min + posting_time).time()
+
+	return datetime.datetime.combine(posting_date, posting_time).replace(microsecond=0)
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 7c2a1f1..3467b82 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -149,6 +149,7 @@
 		self.update_prevdoc_status()
 		self.set_subcontracting_order_status()
 		self.set_consumed_qty_in_subcontract_order()
+		self.make_bundle_using_old_serial_batch_fields()
 		self.update_stock_ledger()
 		self.make_gl_entries()
 		self.repost_future_sle_and_gle()
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
index 5523c31..0450038 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
@@ -643,10 +643,6 @@
 		)
 		scr = make_subcontracting_receipt(sco.name)
 		scr.save()
-		for row in scr.supplied_items:
-			self.assertNotEqual(row.rate, 300.00)
-			self.assertFalse(row.serial_and_batch_bundle)
-
 		scr.submit()
 		scr.reload()
 
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
index 9bfc2fd..f9e0a0b 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
@@ -48,11 +48,14 @@
   "reference_name",
   "section_break_45",
   "serial_and_batch_bundle",
-  "serial_no",
+  "use_serial_batch_fields",
   "col_break5",
   "rejected_serial_and_batch_bundle",
-  "batch_no",
+  "section_break_jshh",
+  "serial_no",
   "rejected_serial_no",
+  "column_break_henr",
+  "batch_no",
   "manufacture_details",
   "manufacturer",
   "column_break_16",
@@ -311,22 +314,20 @@
    "label": "Serial and Batch Details"
   },
   {
-   "depends_on": "eval:!doc.is_fixed_asset",
+   "depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1",
    "fieldname": "serial_no",
    "fieldtype": "Small Text",
    "label": "Serial No",
-   "no_copy": 1,
-   "read_only": 1
+   "no_copy": 1
   },
   {
-   "depends_on": "eval:!doc.is_fixed_asset",
+   "depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1",
    "fieldname": "batch_no",
    "fieldtype": "Link",
    "label": "Batch No",
    "no_copy": 1,
    "options": "Batch",
-   "print_hide": 1,
-   "read_only": 1
+   "print_hide": 1
   },
   {
    "depends_on": "eval: !parent.is_return",
@@ -478,6 +479,7 @@
    "label": "Accounting Details"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
@@ -486,6 +488,7 @@
    "print_hide": 1
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "rejected_serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Rejected Serial and Batch Bundle",
@@ -546,12 +549,27 @@
    "fieldtype": "Check",
    "label": "Include Exploded Items",
    "print_hide": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
+   "fieldname": "section_break_jshh",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_henr",
+   "fieldtype": "Column Break"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-30 12:05:51.920705",
+ "modified": "2024-02-04 16:23:30.374865",
  "modified_by": "Administrator",
  "module": "Subcontracting",
  "name": "Subcontracting Receipt Item",
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py
index d02160e..1a4ce5b 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py
@@ -58,6 +58,7 @@
 		subcontracting_order: DF.Link | None
 		subcontracting_order_item: DF.Data | None
 		subcontracting_receipt_item: DF.Data | None
+		use_serial_batch_fields: DF.Check
 		warehouse: DF.Link | None
 	# end: auto-generated types
 
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
index 90bcf4e..957b6a2 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
@@ -26,10 +26,13 @@
   "current_stock",
   "secbreak_3",
   "serial_and_batch_bundle",
-  "batch_no",
+  "use_serial_batch_fields",
   "col_break4",
+  "subcontracting_order",
+  "section_break_zwnh",
   "serial_no",
-  "subcontracting_order"
+  "column_break_qibi",
+  "batch_no"
  ],
  "fields": [
   {
@@ -60,19 +63,19 @@
    "width": "300px"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "batch_no",
    "fieldtype": "Link",
    "label": "Batch No",
    "no_copy": 1,
-   "options": "Batch",
-   "read_only": 1
+   "options": "Batch"
   },
   {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
    "fieldname": "serial_no",
    "fieldtype": "Text",
    "label": "Serial No",
-   "no_copy": 1,
-   "read_only": 1
+   "no_copy": 1
   },
   {
    "fieldname": "col_break1",
@@ -198,6 +201,7 @@
   },
   {
    "columns": 2,
+   "depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -205,12 +209,27 @@
    "no_copy": 1,
    "options": "Serial and Batch Bundle",
    "print_hide": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "use_serial_batch_fields",
+   "fieldtype": "Check",
+   "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "depends_on": "eval:doc.use_serial_batch_fields === 1",
+   "fieldname": "section_break_zwnh",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_qibi",
+   "fieldtype": "Column Break"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-03-15 13:55:08.132626",
+ "modified": "2024-02-04 16:32:17.534162",
  "modified_by": "Administrator",
  "module": "Subcontracting",
  "name": "Subcontracting Receipt Supplied Item",
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.py b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.py
index 2ee5551..8f09197a 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.py
@@ -35,6 +35,7 @@
 		serial_no: DF.Text | None
 		stock_uom: DF.Link | None
 		subcontracting_order: DF.Link | None
+		use_serial_batch_fields: DF.Check
 	# end: auto-generated types
 
 	pass
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index dc1529b..e4c4d60 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -113,8 +113,8 @@
 				"reference_name": self.name,
 			}
 		)
-		communication.ignore_permissions = True
-		communication.ignore_mandatory = True
+		communication.flags.ignore_permissions = True
+		communication.flags.ignore_mandatory = True
 		communication.save()
 
 	@frappe.whitelist()
diff --git a/pyproject.toml b/pyproject.toml
index 604aa44..8a0f12c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -39,3 +39,6 @@
 use_parentheses = true
 ensure_newline_before_comments = true
 indent = "\t"
+
+[tool.bench.frappe-dependencies]
+frappe = ">=16.0.0-dev,<17.0.0"