Merge pull request #38496 from ruthra-kumar/bisect_accounting_reports

feat: utility to debug financial reports
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 3bc22af..275442a 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -28,4 +28,7 @@
 494bd9ef78313436f0424b918f200dab8fc7c20b
 
 # bulk format python code with black
-baec607ff5905b1c67531096a9cf50ec7ff00a5d
\ No newline at end of file
+baec607ff5905b1c67531096a9cf50ec7ff00a5d
+
+# bulk refactor with sourcery
+eb9ee3f79b94e594fc6dfa4f6514580e125eee8c
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 30be903..6ea121f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -5,7 +5,7 @@
 
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.0.1
+    rev: v4.3.0
     hooks:
       - id: trailing-whitespace
         files: "erpnext.*"
@@ -15,6 +15,10 @@
         args: ['--branch', 'develop']
       - id: check-merge-conflict
       - id: check-ast
+      - id: check-json
+      - id: check-toml
+      - id: check-yaml
+      - id: debug-statements
 
   - repo: https://github.com/pre-commit/mirrors-eslint
     rev: v8.44.0
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 5c58f84..48ebe92 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -36,7 +36,7 @@
 
 	if not frappe.flags.company_cost_center:
 		frappe.flags.company_cost_center = {}
-	if not company in frappe.flags.company_cost_center:
+	if company not in frappe.flags.company_cost_center:
 		frappe.flags.company_cost_center[company] = frappe.get_cached_value(
 			"Company", company, "cost_center"
 		)
@@ -47,7 +47,7 @@
 	"""Returns the default company currency"""
 	if not frappe.flags.company_currency:
 		frappe.flags.company_currency = {}
-	if not company in frappe.flags.company_currency:
+	if company not in frappe.flags.company_currency:
 		frappe.flags.company_currency[company] = frappe.db.get_value(
 			"Company", company, "default_currency", cache=True
 		)
@@ -81,7 +81,7 @@
 	if not hasattr(frappe.local, "enable_perpetual_inventory"):
 		frappe.local.enable_perpetual_inventory = {}
 
-	if not company in frappe.local.enable_perpetual_inventory:
+	if company not in frappe.local.enable_perpetual_inventory:
 		frappe.local.enable_perpetual_inventory[company] = (
 			frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
 		)
@@ -96,7 +96,7 @@
 	if not hasattr(frappe.local, "default_finance_book"):
 		frappe.local.default_finance_book = {}
 
-	if not company in frappe.local.default_finance_book:
+	if company not in frappe.local.default_finance_book:
 		frappe.local.default_finance_book[company] = frappe.get_cached_value(
 			"Company", company, "default_finance_book"
 		)
@@ -108,7 +108,7 @@
 	if not hasattr(frappe.local, "party_account_types"):
 		frappe.local.party_account_types = {}
 
-	if not party_type in frappe.local.party_account_types:
+	if party_type not in frappe.local.party_account_types:
 		frappe.local.party_account_types[party_type] = (
 			frappe.db.get_value("Party Type", party_type, "account_type") or ""
 		)
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index d0940c7..6282e9a 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -232,7 +232,7 @@
 			if amount + already_booked_amount_in_account_currency > item.net_amount:
 				amount = item.net_amount - already_booked_amount_in_account_currency
 
-		if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
+		if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date:
 			partial_month = flt(date_diff(end_date, start_date)) / flt(
 				date_diff(get_last_day(end_date), get_first_day(start_date))
 			)
@@ -358,9 +358,11 @@
 
 		account_currency = get_account_currency(item.expense_account or item.income_account)
 		if doc.doctype == "Sales Invoice":
+			against_type = "Customer"
 			against, project = doc.customer, doc.project
 			credit_account, debit_account = item.income_account, item.deferred_revenue_account
 		else:
+			against_type = "Supplier"
 			against, project = doc.supplier, item.project
 			credit_account, debit_account = item.deferred_expense_account, item.expense_account
 
@@ -413,6 +415,7 @@
 				doc,
 				credit_account,
 				debit_account,
+				against_type,
 				against,
 				amount,
 				base_amount,
@@ -494,6 +497,7 @@
 	doc,
 	credit_account,
 	debit_account,
+	against_type,
 	against,
 	amount,
 	base_amount,
@@ -515,7 +519,9 @@
 		doc.get_gl_dict(
 			{
 				"account": credit_account,
+				"against_type": against_type,
 				"against": against,
+				"against_link": against,
 				"credit": base_amount,
 				"credit_in_account_currency": amount,
 				"cost_center": cost_center,
@@ -534,7 +540,9 @@
 		doc.get_gl_dict(
 			{
 				"account": debit_account,
+				"against_type": against_type,
 				"against": against,
+				"against_link": against,
 				"debit": base_amount,
 				"debit_in_account_currency": amount,
 				"cost_center": cost_center,
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/at_austria_chart_template.json b/erpnext/accounts/doctype/account/chart_of_accounts/unverified/at_austria_chart_template.json
index 58d67be..bd7228e 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/at_austria_chart_template.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/unverified/at_austria_chart_template.json
@@ -26,7 +26,7 @@
 				"0360 Bauliche Investitionen in fremden (gepachteten) Betriebs- und Geschäftsgebäuden": {"account_type": "Fixed Asset"},
 				"0370 Bauliche Investitionen in fremden (gepachteten) Wohn- und Sozialgebäuden": {"account_type": "Fixed Asset"},
 				"0390 Kumulierte Abschreibungen zu Grundstücken ": {"account_type": "Fixed Asset"},
-				"0400 Maschinen und Geräte ": {"account_type": "Fixed Asset"},				
+				"0400 Maschinen und Geräte ": {"account_type": "Fixed Asset"},
 				"0500 Maschinenwerkzeuge ": {"account_type": "Fixed Asset"},
 				"0510 Allgemeine Werkzeuge und Handwerkzeuge ": {"account_type": "Fixed Asset"},
 				"0520 Prototypen, Formen, Modelle ": {"account_type": "Fixed Asset"},
@@ -65,42 +65,41 @@
 				"0980 Geleistete Anzahlungen auf Finanzanlagen ": {"account_type": "Fixed Asset"},
 				"0990 Kumulierte Abschreibungen zu Finanzanlagen ": {"account_type": "Fixed Asset"},
             "root_type": "Asset"
-        },    
+        },
         "Klasse 1 Aktiva: Vorr\u00e4te": {
             "1000 Bezugsverrechnung": {"account_type": "Stock"},
             "1100 Rohstoffe": {"account_type": "Stock"},
             "1200 Bezogene Teile": {"account_type": "Stock"},
             "1300 Hilfsstoffe": {"account_type": "Stock"},
             "1350 Betriebsstoffe": {"account_type": "Stock"},
-            "1360 Vorrat Energietraeger": {"account_type": "Stock"},            
+            "1360 Vorrat Energietraeger": {"account_type": "Stock"},
             "1400 Unfertige Erzeugnisse": {"account_type": "Stock"},
             "1500 Fertige Erzeugnisse": {"account_type": "Stock"},
             "1600 Handelswarenvorrat": {"account_type": "Stock Received But Not Billed"},
             "1700 Noch nicht abrechenbare Leistungen": {"account_type": "Stock"},
-            "1900 Wertberichtigungen": {"account_type": "Stock"},
             "1800 Geleistete Anzahlungen": {"account_type": "Stock"},
             "1900 Wertberichtigungen": {"account_type": "Stock"},
             "root_type": "Asset"
-        },    
+        },
         "Klasse 3 Passiva: Verbindlichkeiten": {
             "3000 Allgemeine Verbindlichkeiten (Schuld)": {"account_type": "Payable"},
             "3010 R\u00fcckstellungen f\u00fcr Pensionen": {"account_type": "Payable"},
             "3020 Steuerr\u00fcckstellungen": {"account_type": "Tax"},
-            "3041 Sonstige R\u00fcckstellungen": {"account_type": "Payable"},            
+            "3041 Sonstige R\u00fcckstellungen": {"account_type": "Payable"},
             "3110 Verbindlichkeiten gegen\u00fcber Bank": {"account_type": "Payable"},
             "3150 Verbindlichkeiten Darlehen": {"account_type": "Payable"},
-            "3185 Verbindlichkeiten Kreditkarte": {"account_type": "Payable"},            
+            "3185 Verbindlichkeiten Kreditkarte": {"account_type": "Payable"},
             "3380 Verbindlichkeiten aus der Annahme gezogener Wechsel u. d. Ausstellungen eigener Wechsel": {
                 "account_type": "Payable"
             },
             "3400 Verbindlichkeiten gegen\u00fc. verb. Untern., Verbindl. gegen\u00fc. Untern., mit denen eine Beteiligungsverh\u00e4lnis besteht": {},
             "3460 Verbindlichkeiten gegenueber Gesellschaftern": {"account_type": "Payable"},
             "3470 Einlagen stiller Gesellschafter": {"account_type": "Payable"},
-            "3585 Verbindlichkeiten Lohnsteuer": {"account_type": "Tax"},             
-            "3590 Verbindlichkeiten Kommunalabgaben": {"account_type": "Tax"},  
-            "3595 Verbindlichkeiten Dienstgeberbeitrag": {"account_type": "Tax"},                       
+            "3585 Verbindlichkeiten Lohnsteuer": {"account_type": "Tax"},
+            "3590 Verbindlichkeiten Kommunalabgaben": {"account_type": "Tax"},
+            "3595 Verbindlichkeiten Dienstgeberbeitrag": {"account_type": "Tax"},
             "3600 Verbindlichkeiten Sozialversicherung": {"account_type": "Payable"},
-            "3640 Verbindlichkeiten Loehne und Gehaelter": {"account_type": "Payable"},            
+            "3640 Verbindlichkeiten Loehne und Gehaelter": {"account_type": "Payable"},
             "3700 Sonstige Verbindlichkeiten": {"account_type": "Payable"},
             "3900 Passive Rechnungsabgrenzungsposten": {"account_type": "Payable"},
             "3100 Anleihen (einschlie\u00dflich konvertibler)": {"account_type": "Payable"},
@@ -119,13 +118,13 @@
             },
             "3515 Umsatzsteuer Inland 10%": {
                 "account_type": "Tax"
-            },            
+            },
             "3520 Umsatzsteuer aus i.g. Erwerb 20%": {
                 "account_type": "Tax"
             },
             "3525 Umsatzsteuer aus i.g. Erwerb 10%": {
                 "account_type": "Tax"
-            },            
+            },
             "3560 Umsatzsteuer-Evidenzkonto f\u00fcr erhaltene Anzahlungen auf Bestellungen": {},
             "3360 Verbindlichkeiten aus Lieferungen u. Leistungen EU": {
                 "account_type": "Payable"
@@ -141,7 +140,7 @@
                 "account_type": "Tax"
             },
             "root_type": "Liability"
-        },      
+        },
         "Klasse 2 Aktiva: Umlaufverm\u00f6gen, Rechnungsabgrenzungen": {
             "2030 Forderungen aus Lieferungen und Leistungen Inland (0% USt, umsatzsteuerfrei)": {
                 "account_type": "Receivable"
@@ -154,7 +153,7 @@
             },
             "2040 Forderungen aus Lieferungen und Leistungen Inland (sonstiger USt-Satz)": {
                 "account_type": "Receivable"
-            },                                    
+            },
             "2100 Forderungen aus Lieferungen und Leistungen EU": {
                 "account_type": "Receivable"
             },
@@ -192,7 +191,7 @@
                 "account_type": "Receivable"
             },
             "2570 Einfuhrumsatzsteuer (bezahlt)": {"account_type": "Tax"},
-            
+
             "2460 Eingeforderte aber noch nicht eingezahlte Einlagen": {
                 "account_type": "Receivable"
             },
@@ -243,10 +242,10 @@
             },
             "2800 Guthaben bei Bank": {
                 "account_type": "Bank"
-            },  
+            },
             "2801 Guthaben bei Bank - Sparkonto": {
                 "account_type": "Bank"
-            },                      
+            },
             "2810 Guthaben bei Paypal": {
                 "account_type": "Bank"
             },
@@ -264,19 +263,19 @@
             },
             "2895 Schwebende Geldbewegugen": {
                 "account_type": "Bank"
-            },            
+            },
             "2513 Vorsteuer Inland 5%": {
                 "account_type": "Tax"
             },
             "2515 Vorsteuer Inland 20%": {
                 "account_type": "Tax"
-            },                        
+            },
             "2520 Vorsteuer aus innergemeinschaftlichem Erwerb 10%": {
                 "account_type": "Tax"
             },
             "2525 Vorsteuer aus innergemeinschaftlichem Erwerb 20%": {
                 "account_type": "Tax"
-            },            
+            },
             "2530 Vorsteuer \u00a719/Art 19 ( reverse charge ) ": {
                 "account_type": "Tax"
             },
@@ -286,16 +285,16 @@
             "root_type": "Asset"
         },
           "Klasse 4: Betriebliche Erträge": {
-            "4000 Erlöse 20 %": {"account_type": "Income Account"},          
-            "4020 Erl\u00f6se 0 % steuerbefreit": {"account_type": "Income Account"},            
+            "4000 Erlöse 20 %": {"account_type": "Income Account"},
+            "4020 Erl\u00f6se 0 % steuerbefreit": {"account_type": "Income Account"},
             "4010 Erl\u00f6se 10 %": {"account_type": "Income Account"},
-            "4030 Erl\u00f6se 13 %": {"account_type": "Income Account"},            
-            "4040 Erl\u00f6se 0 % innergemeinschaftliche Lieferungen": {"account_type": "Income Account"},      
-            "4400 Erl\u00f6sreduktion 0 % steuerbefreit": {"account_type": "Expense Account"},            
+            "4030 Erl\u00f6se 13 %": {"account_type": "Income Account"},
+            "4040 Erl\u00f6se 0 % innergemeinschaftliche Lieferungen": {"account_type": "Income Account"},
+            "4400 Erl\u00f6sreduktion 0 % steuerbefreit": {"account_type": "Expense Account"},
             "4410 Erl\u00f6sreduktion 10 %": {"account_type": "Expense Account"},
             "4420 Erl\u00f6sreduktion 20 %": {"account_type": "Expense Account"},
-            "4430 Erl\u00f6sreduktion 13 %": {"account_type": "Expense Account"},            
-            "4440 Erl\u00f6sreduktion 0 % innergemeinschaftliche Lieferungen": {"account_type": "Expense Account"}, 
+            "4430 Erl\u00f6sreduktion 13 %": {"account_type": "Expense Account"},
+            "4440 Erl\u00f6sreduktion 0 % innergemeinschaftliche Lieferungen": {"account_type": "Expense Account"},
             "4500 Ver\u00e4nderungen des Bestandes an fertigen und unfertigen Erzeugn. sowie an noch nicht abrechenbaren Leistungen": {"account_type": "Income Account"},
             "4580 Aktivierte Eigenleistungen": {"account_type": "Income Account"},
             "4600 Erl\u00f6se aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {"account_type": "Income Account"},
@@ -304,15 +303,15 @@
             "4700 Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen": {"account_type": "Income Account"},
             "4800 \u00dcbrige betriebliche Ertr\u00e4ge": {"account_type": "Income Account"},
             "root_type": "Income"
-        },        
+        },
         "Klasse 5: Aufwand f\u00fcr Material und Leistungen": {
-            "5000 Einkauf Partnerleistungen": {"account_type": "Cost of Goods Sold"},        
+            "5000 Einkauf Partnerleistungen": {"account_type": "Cost of Goods Sold"},
             "5100 Verbrauch an Rohstoffen": {"account_type": "Cost of Goods Sold"},
             "5200 Verbrauch von bezogenen Fertig- und Einzelteilen": {"account_type": "Cost of Goods Sold"},
             "5300 Verbrauch von Hilfsstoffen": {"account_type": "Cost of Goods Sold"},
             "5340 Verbrauch Verpackungsmaterial": {"account_type": "Cost of Goods Sold"},
             "5470 Verbrauch von Kleinmaterial": {"account_type": "Cost of Goods Sold"},
-            "5450 Verbrauch von Reinigungsmaterial": {"account_type": "Cost of Goods Sold"},                                  
+            "5450 Verbrauch von Reinigungsmaterial": {"account_type": "Cost of Goods Sold"},
             "5400 Verbrauch von Betriebsstoffen": {"account_type": "Cost of Goods Sold"},
             "5500 Verbrauch von Werkzeugen und anderen Erzeugungshilfsmittel": {"account_type": "Cost of Goods Sold"},
             "5600 Verbrauch von Brenn- und Treibstoffen, Energie und Wasser": {"account_type": "Cost of Goods Sold"},
@@ -340,7 +339,7 @@
             "6700 Sonstige Sozialaufwendungen": {"account_type": "Payable"},
             "6900 Aufwandsstellenrechnung Personal": {"account_type": "Payable"},
             "root_type": "Expense"
-        },       
+        },
          "Klasse 7: Abschreibungen und sonstige betriebliche Aufwendungen": {
             "7010 Abschreibungen auf das Anlageverm\u00f6gen (ausgenommen Finanzanlagen)": {"account_type": "Depreciation"},
             "7100 Sonstige Steuern und Geb\u00fchren": {"account_type": "Tax"},
@@ -349,7 +348,7 @@
             "7310 Fahrrad - Aufwand": {"account_type": "Expense Account"},
             "7320 Kfz - Aufwand": {"account_type": "Expense Account"},
             "7330 LKW - Aufwand": {"account_type": "Expense Account"},
-            "7340 Lastenrad - Aufwand": {"account_type": "Expense Account"},            
+            "7340 Lastenrad - Aufwand": {"account_type": "Expense Account"},
             "7350 Reise- und Fahraufwand": {"account_type": "Expense Account"},
             "7360 Tag- und N\u00e4chtigungsgelder": {"account_type": "Expense Account"},
             "7380 Nachrichtenaufwand": {"account_type": "Expense Account"},
@@ -409,7 +408,7 @@
             "8990 Gewinnabfuhr bzw. Verlust\u00fcberrechnung aus Ergebnisabf\u00fchrungsvertr\u00e4gen": {"account_type": "Expense Account"},
             "8350 nicht ausgenutzte Lieferantenskonti": {"account_type": "Expense Account"},
             "root_type": "Income"
-        },       
+        },
         "Klasse 9 Passiva: Eigenkapital, R\u00fccklagen, stille Einlagen, Abschlusskonten": {
             "9000 Gezeichnetes bzw. gewidmetes Kapital": {
                 "account_type": "Equity"
@@ -435,5 +434,5 @@
             },
             "root_type": "Equity"
         }
-     }   
+     }
   }
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ca_plan_comptable_pour_les_provinces_francophones.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ca_plan_comptable_pour_les_provinces_francophones.json
index 2811fc5..2a30cbc 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ca_plan_comptable_pour_les_provinces_francophones.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ca_plan_comptable_pour_les_provinces_francophones.json
@@ -33,7 +33,9 @@
                 }, 
                 "Stocks": {
                     "Mati\u00e8res premi\u00e8res": {}, 
-                    "Stock de produits fini": {}, 
+                    "Stock de produits fini": {
+                        "account_type": "Stock"
+                    }, 
                     "Stock exp\u00e9di\u00e9 non-factur\u00e9": {}, 
                     "Travaux en cours": {}, 
                     "account_type": "Stock"
@@ -395,9 +397,11 @@
         }, 
         "Produits": {
             "Revenus de ventes": {
-                " Escomptes de volume sur ventes": {}, 
+                "Escomptes de volume sur ventes": {}, 
                 "Autres produits d'exploitation": {}, 
-                "Ventes": {}, 
+                "Ventes": {
+                    "account_type": "Income Account"
+                }, 
                 "Ventes avec des provinces harmonis\u00e9es": {}, 
                 "Ventes avec des provinces non-harmonis\u00e9es": {}, 
                 "Ventes \u00e0 l'\u00e9tranger": {}
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json
index 741d428..daf2e21 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json
@@ -53,8 +53,13 @@
 				},
 				"II. Forderungen und sonstige Vermögensgegenstände": {
 					"is_group": 1,
-					"Ford. a. Lieferungen und Leistungen": {
+					"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": {
 						"account_number": "1400",
+						"account_type": "Receivable",
+						"is_group": 1
+					},
+					"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
+						"account_number": "1410",
 						"account_type": "Receivable"
 					},
 					"Durchlaufende Posten": {
@@ -180,8 +185,13 @@
 				},
 				"IV. Verbindlichkeiten aus Lieferungen und Leistungen": {
 					"is_group": 1,
-					"Verbindlichkeiten aus Lieferungen u. Leistungen": {
+					"Verbindlichkeiten aus Lieferungen und Leistungen mit Kontokorrent": {
 						"account_number": "1600",
+						"account_type": "Payable",
+						"is_group": 1
+					},
+					"Verbindlichkeiten aus Lieferungen und Leistungen ohne Kontokorrent": {
+						"account_number": "1610",
 						"account_type": "Payable"
 					}
 				},
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json
index 2bf55cf..000ef80 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json
@@ -407,13 +407,10 @@
                         "Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
                             "account_number": "9960"
                         },
-                        "Debitoren": {
-                            "is_group": 1,
-                            "account_number": "10000"
-                        },
-                        "Forderungen aus Lieferungen und Leistungen": {
+                        "Forderungen aus Lieferungen und Leistungen mit Kontokorrent": {
                             "account_number": "1200",
-                            "account_type": "Receivable"
+                            "account_type": "Receivable",
+                            "is_group": 1
                         },
                         "Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
                             "account_number": "1210"
@@ -1138,18 +1135,15 @@
                     "Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
                         "account_number": "9964"
                     },
-                    "Kreditoren": {
-                        "account_number": "70000",
+                    "Verb. aus Lieferungen und Leistungen mit Kontokorrent": {
+                        "account_number": "3300",
+                        "account_type": "Payable",
                         "is_group": 1,
-                        "Wareneingangs-­Verrechnungskonto" : {
+                        "Wareneingangs-Verrechnungskonto" : {
                             "account_number": "70001",
                             "account_type": "Stock Received But Not Billed"
                         }
                     },
-                    "Verb. aus Lieferungen und Leistungen": {
-                        "account_number": "3300",
-                        "account_type": "Payable"
-                    },
                     "Verb. aus Lieferungen und Leistungen ohne Kontokorrent": {
                         "account_number": "3310"
                     },
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index 4f7eeba..8be09db 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -44,7 +44,7 @@
 		self.set_total_gain_loss()
 
 	def validate_rounding_loss_allowance(self):
-		if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1):
+		if self.rounding_loss_allowance < 0 or self.rounding_loss_allowance >= 1:
 			frappe.throw(_("Rounding Loss Allowance should be between 0 and 1"))
 
 	def set_total_gain_loss(self):
@@ -214,7 +214,7 @@
 				# round off balance based on currency precision
 				# and consider debit-credit difference allowance
 				currency_precision = get_currency_precision()
-				rounding_loss_allowance = float(rounding_loss_allowance) or 0.05
+				rounding_loss_allowance = float(rounding_loss_allowance)
 				for acc in account_details:
 					acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision)
 					if abs(acc.balance_in_account_currency) <= rounding_loss_allowance:
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json
index 5063ec6..09912e9 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.json
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json
@@ -17,10 +17,13 @@
   "account_currency",
   "debit_in_account_currency",
   "credit_in_account_currency",
+  "against_type",
   "against",
+  "against_link",
   "against_voucher_type",
   "against_voucher",
   "voucher_type",
+  "voucher_subtype",
   "voucher_no",
   "voucher_detail_no",
   "project",
@@ -129,6 +132,13 @@
    "options": "account_currency"
   },
   {
+   "fieldname": "against_type",
+   "fieldtype": "Link",
+   "in_filter": 1,
+   "label": "Against Type",
+   "options": "DocType"
+  },
+  {
    "fieldname": "against",
    "fieldtype": "Text",
    "in_filter": 1,
@@ -137,13 +147,19 @@
    "oldfieldtype": "Text"
   },
   {
+   "fieldname": "against_link",
+   "fieldtype": "Dynamic Link",
+   "in_filter": 1,
+   "label": "Against",
+   "options": "against_type"
+  },
+  {
    "fieldname": "against_voucher_type",
    "fieldtype": "Link",
    "label": "Against Voucher Type",
    "oldfieldname": "against_voucher_type",
    "oldfieldtype": "Data",
-   "options": "DocType",
-   "search_index": 1
+   "options": "DocType"
   },
   {
    "fieldname": "against_voucher",
@@ -162,8 +178,7 @@
    "label": "Voucher Type",
    "oldfieldname": "voucher_type",
    "oldfieldtype": "Select",
-   "options": "DocType",
-   "search_index": 1
+   "options": "DocType"
   },
   {
    "fieldname": "voucher_no",
@@ -280,13 +295,18 @@
    "fieldtype": "Currency",
    "label": "Credit Amount in Transaction Currency",
    "options": "transaction_currency"
+  },
+  {
+   "fieldname": "voucher_subtype",
+   "fieldtype": "Small Text",
+   "label": "Voucher Subtype"
   }
  ],
  "icon": "fa fa-list",
  "idx": 1,
  "in_create": 1,
  "links": [],
- "modified": "2023-08-16 21:38:44.072267",
+ "modified": "2023-12-18 15:38:14.006208",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "GL Entry",
@@ -321,4 +341,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "states": []
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index f7dd29a..139f526 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -39,6 +39,8 @@
 		account: DF.Link | None
 		account_currency: DF.Link | None
 		against: DF.Text | None
+		against_link: DF.DynamicLink | None
+		against_type: DF.Link | None
 		against_voucher: DF.DynamicLink | None
 		against_voucher_type: DF.Link | None
 		company: DF.Link | None
@@ -66,6 +68,7 @@
 		transaction_exchange_rate: DF.Float
 		voucher_detail_no: DF.Data | None
 		voucher_no: DF.DynamicLink | None
+		voucher_subtype: DF.SmallText | None
 		voucher_type: DF.Link | None
 	# end: auto-generated types
 
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
index 76f4dad..69b0860 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
@@ -153,7 +153,9 @@
 							"account": inv.debit_to,
 							"party_type": "Customer",
 							"party": d.customer,
+							"against_type": "Account",
 							"against": self.accounts_receivable_credit,
+							"against_link": self.accounts_receivable_credit,
 							"credit": outstanding_in_company_currency,
 							"credit_in_account_currency": outstanding_in_company_currency
 							if inv.party_account_currency == company_currency
@@ -173,7 +175,9 @@
 							"account": self.accounts_receivable_credit,
 							"party_type": "Customer",
 							"party": d.customer,
+							"against_type": "Account",
 							"against": inv.debit_to,
+							"against_link": inv.debit_to,
 							"debit": outstanding_in_company_currency,
 							"debit_in_account_currency": outstanding_in_company_currency
 							if ar_credit_account_currency == company_currency
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 9684a0d..c97a8dc 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -8,7 +8,7 @@
 frappe.ui.form.on("Journal Entry", {
 	setup: function(frm) {
 		frm.add_fetch("bank_account", "account", "account");
-		frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger"];
+		frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
 	},
 
 	refresh: function(frm) {
@@ -220,6 +220,16 @@
 			return erpnext.journal_entry.account_query(me.frm);
 		});
 
+		me.frm.set_query("against_account_link", "accounts", function(doc, cdt, cdn) {
+			return erpnext.journal_entry.against_account_query(me.frm);
+		});
+
+		me.frm.set_query("against_type", "accounts", function(){
+			return {
+				query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_type",
+			}
+		})
+
 		me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
 			const row = locals[cdt][cdn];
 
@@ -591,6 +601,21 @@
 		return { filters: filters };
 	},
 
+	against_account_query: function(frm) {
+		if (frm.doc.against_type != "Account"){
+			return { filters: {} };
+		}
+		else {
+			let filters = { company: frm.doc.company, is_group: 0 };
+			if(!frm.doc.multi_currency) {
+				$.extend(filters, {
+					account_currency: ['in', [frappe.get_doc(":Company", frm.doc.company).default_currency, null]]
+				});
+			}
+			return { filters: filters };
+		}
+	},
+
 	reverse_journal_entry: function() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index e4f1645..79f1ab0 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -170,6 +170,8 @@
 			"Repost Payment Ledger Items",
 			"Repost Accounting Ledger",
 			"Repost Accounting Ledger Items",
+			"Unreconcile Payment",
+			"Unreconcile Payment Entries",
 		)
 		self.make_gl_entries(1)
 		self.update_advance_paid()
@@ -302,6 +304,7 @@
 					"account": tax_withholding_details.get("account_head"),
 					rev_debit_or_credit: tax_withholding_details.get("tax_amount"),
 					"against_account": parties[0],
+					"against_account_link": parties[0],
 				},
 			)
 
@@ -629,7 +632,7 @@
 					)
 
 				# set totals
-				if not d.reference_name in self.reference_totals:
+				if d.reference_name not in self.reference_totals:
 					self.reference_totals[d.reference_name] = 0.0
 
 				if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"):
@@ -748,27 +751,90 @@
 					)
 
 	def set_against_account(self):
-		accounts_debited, accounts_credited = [], []
 		if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
 			for d in self.get("accounts"):
 				if d.reference_type == "Sales Invoice":
-					field = "customer"
+					against_type = "Customer"
 				else:
-					field = "supplier"
+					against_type = "Supplier"
 
-				d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field)
+				against_account = frappe.db.get_value(d.reference_type, d.reference_name, against_type.lower())
+				d.against_type = against_type
+				d.against_account_link = against_account
 		else:
-			for d in self.get("accounts"):
-				if flt(d.debit) > 0:
-					accounts_debited.append(d.party or d.account)
-				if flt(d.credit) > 0:
-					accounts_credited.append(d.party or d.account)
+			self.get_debited_credited_accounts()
+			if len(self.accounts_credited) > 1 and len(self.accounts_debited) > 1:
+				self.auto_set_against_accounts()
+				return
+			self.get_against_accounts()
 
-			for d in self.get("accounts"):
-				if flt(d.debit) > 0:
-					d.against_account = ", ".join(list(set(accounts_credited)))
-				if flt(d.credit) > 0:
-					d.against_account = ", ".join(list(set(accounts_debited)))
+	def auto_set_against_accounts(self):
+		for i in range(0, len(self.accounts), 2):
+			acc = self.accounts[i]
+			against_acc = self.accounts[i + 1]
+			if acc.debit_in_account_currency > 0:
+				current_val = acc.debit_in_account_currency * flt(acc.exchange_rate)
+				against_val = against_acc.credit_in_account_currency * flt(against_acc.exchange_rate)
+			else:
+				current_val = acc.credit_in_account_currency * flt(acc.exchange_rate)
+				against_val = against_acc.debit_in_account_currency * flt(against_acc.exchange_rate)
+
+			if current_val == against_val:
+				acc.against_type = against_acc.party_type or "Account"
+				against_acc.against_type = acc.party_type or "Account"
+
+				acc.against_account_link = against_acc.party or against_acc.account
+				against_acc.against_account_link = acc.party or acc.account
+			else:
+				frappe.msgprint(
+					_(
+						"Unable to automatically determine {0} accounts. Set them up in the {1} table if needed."
+					).format(frappe.bold("against"), frappe.bold("Accounting Entries")),
+					alert=True,
+				)
+				break
+
+	def get_against_accounts(self):
+		self.against_accounts = []
+		self.split_account = {}
+		self.get_debited_credited_accounts()
+
+		if self.separate_against_account_entries:
+			no_of_credited_acc, no_of_debited_acc = len(self.accounts_credited), len(self.accounts_debited)
+			if no_of_credited_acc <= 1 and no_of_debited_acc <= 1:
+				self.set_against_accounts_for_single_dr_cr()
+				self.separate_against_account_entries = 0
+			elif no_of_credited_acc == 1:
+				self.against_accounts = self.accounts_debited
+				self.split_account = self.accounts_credited[0]
+			elif no_of_debited_acc == 1:
+				self.against_accounts = self.accounts_credited
+				self.split_account = self.accounts_debited[0]
+
+	def get_debited_credited_accounts(self):
+		self.accounts_debited, self.accounts_credited = [], []
+		self.separate_against_account_entries = 1
+		for d in self.get("accounts"):
+			if flt(d.debit) > 0:
+				self.accounts_debited.append(d)
+			elif flt(d.credit) > 0:
+				self.accounts_credited.append(d)
+
+			if d.against_account_link:
+				self.separate_against_account_entries = 0
+				break
+
+	def set_against_accounts_for_single_dr_cr(self):
+		against_account = None
+		for d in self.accounts:
+			if flt(d.debit) > 0:
+				against_account = self.accounts_credited[0]
+			elif flt(d.credit) > 0:
+				against_account = self.accounts_debited[0]
+			if against_account:
+				d.against_type = against_account.party_type or "Account"
+				d.against_account = against_account.party or against_account.account
+				d.against_account_link = against_account.party or against_account.account
 
 	def validate_debit_credit_amount(self):
 		if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
@@ -965,40 +1031,82 @@
 
 	def build_gl_map(self):
 		gl_map = []
+		self.get_against_accounts()
 		for d in self.get("accounts"):
 			if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
 				r = [d.user_remark, self.remark]
 				r = [x for x in r if x]
 				remarks = "\n".join(r)
 
-				gl_map.append(
-					self.get_gl_dict(
-						{
-							"account": d.account,
-							"party_type": d.party_type,
-							"due_date": self.due_date,
-							"party": d.party,
-							"against": d.against_account,
-							"debit": flt(d.debit, d.precision("debit")),
-							"credit": flt(d.credit, d.precision("credit")),
-							"account_currency": d.account_currency,
-							"debit_in_account_currency": flt(
-								d.debit_in_account_currency, d.precision("debit_in_account_currency")
-							),
-							"credit_in_account_currency": flt(
-								d.credit_in_account_currency, d.precision("credit_in_account_currency")
-							),
-							"against_voucher_type": d.reference_type,
-							"against_voucher": d.reference_name,
-							"remarks": remarks,
-							"voucher_detail_no": d.reference_detail_no,
-							"cost_center": d.cost_center,
-							"project": d.project,
-							"finance_book": self.finance_book,
-						},
-						item=d,
-					)
+				gl_dict = self.get_gl_dict(
+					{
+						"account": d.account,
+						"party_type": d.party_type,
+						"due_date": self.due_date,
+						"party": d.party,
+						"debit": flt(d.debit, d.precision("debit")),
+						"credit": flt(d.credit, d.precision("credit")),
+						"account_currency": d.account_currency,
+						"debit_in_account_currency": flt(
+							d.debit_in_account_currency, d.precision("debit_in_account_currency")
+						),
+						"credit_in_account_currency": flt(
+							d.credit_in_account_currency, d.precision("credit_in_account_currency")
+						),
+						"against_voucher_type": d.reference_type,
+						"against_voucher": d.reference_name,
+						"remarks": remarks,
+						"voucher_detail_no": d.reference_detail_no,
+						"cost_center": d.cost_center,
+						"project": d.project,
+						"finance_book": self.finance_book,
+					},
+					item=d,
 				)
+
+				if not self.separate_against_account_entries:
+					gl_dict.update(
+						{
+							"against_type": d.against_type,
+							"against_link": d.against_account_link,
+						}
+					)
+					gl_map.append(gl_dict)
+
+				elif d in self.against_accounts:
+					gl_dict.update(
+						{
+							"against_type": self.split_account.get("party_type") or "Account",
+							"against": self.split_account.get("party") or self.split_account.get("account"),
+							"against_link": self.split_account.get("party") or self.split_account.get("account"),
+						}
+					)
+					gl_map.append(gl_dict)
+
+				else:
+					for against_account in self.against_accounts:
+						against_account = against_account.as_dict()
+						debit = against_account.credit or against_account.credit_in_account_currency
+						credit = against_account.debit or against_account.debit_in_account_currency
+						gl_dict = gl_dict.copy()
+						gl_dict.update(
+							{
+								"against_type": against_account.party_type or "Account",
+								"against": against_account.party or against_account.account,
+								"against_link": against_account.party or against_account.account,
+								"debit": flt(debit, d.precision("debit")),
+								"credit": flt(credit, d.precision("credit")),
+								"account_currency": d.account_currency,
+								"debit_in_account_currency": flt(
+									debit / d.exchange_rate, d.precision("debit_in_account_currency")
+								),
+								"credit_in_account_currency": flt(
+									credit / d.exchange_rate, d.precision("credit_in_account_currency")
+								),
+							}
+						)
+						gl_map.append(gl_dict)
+
 		return gl_map
 
 	def make_gl_entries(self, cancel=0, adv_adj=0):
@@ -1623,3 +1731,10 @@
 	)
 
 	return doclist
+
+
+@frappe.whitelist()
+def get_against_type(doctype, txt, searchfield, start, page_len, filters):
+	against_types = frappe.db.get_list("Party Type", pluck="name") + ["Account"]
+	doctype = frappe.qb.DocType("DocType")
+	return frappe.qb.from_(doctype).select(doctype.name).where(doctype.name.isin(against_types)).run()
diff --git a/erpnext/accounts/doctype/journal_entry/test_records.json b/erpnext/accounts/doctype/journal_entry/test_records.json
index dafcf56..717c579 100644
--- a/erpnext/accounts/doctype/journal_entry/test_records.json
+++ b/erpnext/accounts/doctype/journal_entry/test_records.json
@@ -1,97 +1,94 @@
 [
- {
-  "cheque_date": "2013-03-14",
-  "cheque_no": "33",
-  "company": "_Test Company",
-  "doctype": "Journal Entry",
-  "accounts": [
-   {
-    "account": "Debtors - _TC",
-	  "party_type": "Customer",
-	  "party": "_Test Customer",
-    "credit_in_account_currency": 400.0,
-    "debit_in_account_currency": 0.0,
-    "doctype": "Journal Entry Account",
-    "parentfield": "accounts",
-    "cost_center": "_Test Cost Center - _TC"
-   },
-   {
-    "account": "_Test Bank - _TC",
-    "credit_in_account_currency": 0.0,
-    "debit_in_account_currency": 400.0,
-    "doctype": "Journal Entry Account",
-    "parentfield": "accounts",
-    "cost_center": "_Test Cost Center - _TC"
-   }
-  ],
-  "naming_series": "_T-Journal Entry-",
-  "posting_date": "2013-02-14",
-  "user_remark": "test",
-  "voucher_type": "Bank Entry"
- },
- 
- 
- {
-  "cheque_date": "2013-02-14",
-  "cheque_no": "33",
-  "company": "_Test Company",
-  "doctype": "Journal Entry",
-  "accounts": [
-   {
-    "account": "_Test Payable - _TC",
-  	"party_type": "Supplier",
-	  "party": "_Test Supplier",
-    "credit_in_account_currency": 0.0,
-    "debit_in_account_currency": 400.0,
-    "doctype": "Journal Entry Account",
-    "parentfield": "accounts",
-    "cost_center": "_Test Cost Center - _TC"
-   },
-   {
-    "account": "_Test Bank - _TC",
-    "credit_in_account_currency": 400.0,
-    "debit_in_account_currency": 0.0,
-    "doctype": "Journal Entry Account",
-    "parentfield": "accounts",
-    "cost_center": "_Test Cost Center - _TC"
-   }
-  ],
-  "naming_series": "_T-Journal Entry-",
-  "posting_date": "2013-02-14",
-  "user_remark": "test",
-  "voucher_type": "Bank Entry"
- },
- 
- 
- {
-  "cheque_date": "2013-02-14",
-  "cheque_no": "33",
-  "company": "_Test Company",
-  "doctype": "Journal Entry",
-  "accounts": [
-   {
-    "account": "Debtors - _TC",
-	  "party_type": "Customer",
-  	"party": "_Test Customer",
-    "credit_in_account_currency": 0.0,
-    "debit_in_account_currency": 400.0,
-    "doctype": "Journal Entry Account",
-    "parentfield": "accounts",
-    "cost_center": "_Test Cost Center - _TC"
-   },
-   {
-    "account": "Sales - _TC",
-    "cost_center": "_Test Cost Center - _TC",
-    "credit_in_account_currency": 400.0,
-    "debit_in_account_currency": 0.0,
-    "doctype": "Journal Entry Account",
-    "parentfield": "accounts",
-    "cost_center": "_Test Cost Center - _TC"
-   }
-  ],
-  "naming_series": "_T-Journal Entry-",
-  "posting_date": "2013-02-14",
-  "user_remark": "test",
-  "voucher_type": "Bank Entry"
- }
+	{
+		"cheque_date": "2013-03-14",
+		"cheque_no": "33",
+		"company": "_Test Company",
+		"doctype": "Journal Entry",
+		"accounts": [
+			{
+				"account": "Debtors - _TC",
+				"party_type": "Customer",
+				"party": "_Test Customer",
+				"credit_in_account_currency": 400.0,
+				"debit_in_account_currency": 0.0,
+				"doctype": "Journal Entry Account",
+				"parentfield": "accounts",
+				"cost_center": "_Test Cost Center - _TC"
+			},
+			{
+				"account": "_Test Bank - _TC",
+				"credit_in_account_currency": 0.0,
+				"debit_in_account_currency": 400.0,
+				"doctype": "Journal Entry Account",
+				"parentfield": "accounts",
+				"cost_center": "_Test Cost Center - _TC"
+			}
+		],
+		"naming_series": "_T-Journal Entry-",
+		"posting_date": "2013-02-14",
+		"user_remark": "test",
+		"voucher_type": "Bank Entry"
+	},
+
+	{
+		"cheque_date": "2013-02-14",
+		"cheque_no": "33",
+		"company": "_Test Company",
+		"doctype": "Journal Entry",
+		"accounts": [
+			{
+				"account": "_Test Payable - _TC",
+				"party_type": "Supplier",
+				"party": "_Test Supplier",
+				"credit_in_account_currency": 0.0,
+				"debit_in_account_currency": 400.0,
+				"doctype": "Journal Entry Account",
+				"parentfield": "accounts",
+				"cost_center": "_Test Cost Center - _TC"
+			},
+			{
+				"account": "_Test Bank - _TC",
+				"credit_in_account_currency": 400.0,
+				"debit_in_account_currency": 0.0,
+				"doctype": "Journal Entry Account",
+				"parentfield": "accounts",
+				"cost_center": "_Test Cost Center - _TC"
+			}
+		],
+		"naming_series": "_T-Journal Entry-",
+		"posting_date": "2013-02-14",
+		"user_remark": "test",
+		"voucher_type": "Bank Entry"
+	},
+
+	{
+		"cheque_date": "2013-02-14",
+		"cheque_no": "33",
+		"company": "_Test Company",
+		"doctype": "Journal Entry",
+		"accounts": [
+			{
+				"account": "Debtors - _TC",
+				"party_type": "Customer",
+				"party": "_Test Customer",
+				"credit_in_account_currency": 0.0,
+				"debit_in_account_currency": 400.0,
+				"doctype": "Journal Entry Account",
+				"parentfield": "accounts",
+				"cost_center": "_Test Cost Center - _TC"
+			},
+			{
+				"account": "Sales - _TC",
+				"credit_in_account_currency": 400.0,
+				"debit_in_account_currency": 0.0,
+				"doctype": "Journal Entry Account",
+				"parentfield": "accounts",
+				"cost_center": "_Test Cost Center - _TC"
+			}
+		],
+		"naming_series": "_T-Journal Entry-",
+		"posting_date": "2013-02-14",
+		"user_remark": "test",
+		"voucher_type": "Bank Entry"
+	}
 ]
diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
index 3132fe9..01006bd 100644
--- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
+++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
@@ -37,7 +37,9 @@
   "col_break3",
   "is_advance",
   "user_remark",
-  "against_account"
+  "against_type",
+  "against_account",
+  "against_account_link"
  ],
  "fields": [
   {
@@ -250,14 +252,21 @@
    "print_hide": 1
   },
   {
-   "fieldname": "against_account",
-   "fieldtype": "Text",
-   "hidden": 1,
+    "fieldname": "against_account",
+    "fieldtype": "Text",
+    "hidden": 1,
+    "label": "Against Account",
+    "no_copy": 1,
+    "oldfieldname": "against_account",
+    "oldfieldtype": "Text",
+    "print_hide": 1
+  },
+  {
+   "fieldname": "against_account_link",
+   "fieldtype": "Dynamic Link",
    "label": "Against Account",
    "no_copy": 1,
-   "oldfieldname": "against_account",
-   "oldfieldtype": "Text",
-   "print_hide": 1
+   "options": "against_type"
   },
   {
    "collapsible": 1,
@@ -280,14 +289,19 @@
    "fieldtype": "Data",
    "hidden": 1,
    "label": "Reference Detail No",
-   "no_copy": 1,
-   "search_index": 1
+   "no_copy": 1
+  },
+  {
+   "fieldname": "against_type",
+   "fieldtype": "Link",
+   "label": "Against Type",
+   "options": "DocType"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-23 11:44:25.841187",
+ "modified": "2023-12-02 23:21:22.205409",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Journal Entry Account",
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
index 3641ac4..cbfb17b 100644
--- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
@@ -140,7 +140,7 @@
 			"Loyalty Point Entry",
 			{"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer},
 		)
-		self.assertEqual(True, not (lpe is None))
+		self.assertEqual(True, lpe is not None)
 
 		# cancelling sales invoice
 		si.cancel()
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 82552bf..11c7c17 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -9,6 +9,8 @@
 from frappe import ValidationError, _, qb, scrub, throw
 from frappe.utils import cint, comma_or, flt, getdate, nowdate
 from frappe.utils.data import comma_and, fmt_money
+from pypika import Case
+from pypika.functions import Coalesce, Sum
 
 import erpnext
 from erpnext.accounts.doctype.bank_account.bank_account import (
@@ -104,9 +106,17 @@
 		self.set_status()
 
 	def set_liability_account(self):
-		if not self.book_advance_payments_in_separate_party_account:
+		# Auto setting liability account should only be done during 'draft' status
+		if self.docstatus > 0:
 			return
 
+		if not frappe.db.get_value(
+			"Company", self.company, "book_advance_payments_in_separate_party_account"
+		):
+			return
+
+		# Important to set this flag for the gl building logic to work properly
+		self.book_advance_payments_in_separate_party_account = True
 		account_type = frappe.get_value(
 			"Account", {"name": self.party_account, "company": self.company}, "account_type"
 		)
@@ -116,11 +126,13 @@
 		):
 			return
 
-		if self.unallocated_amount == 0:
-			for d in self.references:
-				if d.reference_doctype in ["Sales Order", "Purchase Order"]:
-					break
-			else:
+		if self.references:
+			allowed_types = frozenset(["Sales Order", "Purchase Order"])
+			reference_types = set([x.reference_doctype for x in self.references])
+
+			# If there are referencers other than `allowed_types`, treat this as a normal payment entry
+			if reference_types - allowed_types:
+				self.book_advance_payments_in_separate_party_account = False
 				return
 
 		liability_account = get_party_account(
@@ -357,12 +369,12 @@
 				self.set(self.party_account_field, party_account)
 				self.party_account = party_account
 
-		if self.paid_from and not (self.paid_from_account_currency or self.paid_from_account_balance):
+		if self.paid_from and not self.paid_from_account_currency and not self.paid_from_account_balance:
 			acc = get_account_details(self.paid_from, self.posting_date, self.cost_center)
 			self.paid_from_account_currency = acc.account_currency
 			self.paid_from_account_balance = acc.account_balance
 
-		if self.paid_to and not (self.paid_to_account_currency or self.paid_to_account_balance):
+		if self.paid_to and not self.paid_to_account_currency and not self.paid_to_account_balance:
 			acc = get_account_details(self.paid_to, self.posting_date, self.cost_center)
 			self.paid_to_account_currency = acc.account_currency
 			self.paid_to_account_balance = acc.account_balance
@@ -378,8 +390,9 @@
 	) -> None:
 		for d in self.get("references"):
 			if d.allocated_amount:
-				if update_ref_details_only_for and (
-					not (d.reference_doctype, d.reference_name) in update_ref_details_only_for
+				if (
+					update_ref_details_only_for
+					and (d.reference_doctype, d.reference_name) not in update_ref_details_only_for
 				):
 					continue
 
@@ -690,7 +703,7 @@
 		self.db_set("status", self.status, update_modified=True)
 
 	def set_tax_withholding(self):
-		if not self.party_type == "Supplier":
+		if self.party_type != "Supplier":
 			return
 
 		if not self.apply_tax_withholding_amount:
@@ -781,7 +794,7 @@
 		self.base_received_amount = self.base_paid_amount
 		if (
 			self.paid_from_account_currency == self.paid_to_account_currency
-			and not self.payment_type == "Internal Transfer"
+			and self.payment_type != "Internal Transfer"
 		):
 			self.received_amount = self.paid_amount
 
@@ -1048,7 +1061,9 @@
 					"account": self.party_account,
 					"party_type": self.party_type,
 					"party": self.party,
+					"against_type": "Account",
 					"against": against_account,
+					"against_link": against_account,
 					"account_currency": self.party_account_currency,
 					"cost_center": self.cost_center,
 				},
@@ -1213,7 +1228,9 @@
 					{
 						"account": self.paid_from,
 						"account_currency": self.paid_from_account_currency,
+						"against_type": self.party_type if self.payment_type == "Pay" else "Account",
 						"against": self.party if self.payment_type == "Pay" else self.paid_to,
+						"against_link": self.party if self.payment_type == "Pay" else self.paid_to,
 						"credit_in_account_currency": self.paid_amount,
 						"credit": self.base_paid_amount,
 						"cost_center": self.cost_center,
@@ -1228,7 +1245,9 @@
 					{
 						"account": self.paid_to,
 						"account_currency": self.paid_to_account_currency,
+						"against_type": self.party_type if self.payment_type == "Receive" else "Account",
 						"against": self.party if self.payment_type == "Receive" else self.paid_from,
+						"against_link": self.party if self.payment_type == "Receive" else self.paid_from,
 						"debit_in_account_currency": self.received_amount,
 						"debit": self.base_received_amount,
 						"cost_center": self.cost_center,
@@ -1252,6 +1271,7 @@
 				rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
 				against = self.party or self.paid_to
 
+			against_type = self.party_type or "Account"
 			payment_account = self.get_party_account_for_taxes()
 			tax_amount = d.tax_amount
 			base_tax_amount = d.base_tax_amount
@@ -1260,7 +1280,9 @@
 				self.get_gl_dict(
 					{
 						"account": d.account_head,
+						"against_type": against_type,
 						"against": against,
+						"against_link": against,
 						dr_or_cr: tax_amount,
 						dr_or_cr + "_in_account_currency": base_tax_amount
 						if account_currency == self.company_currency
@@ -1285,7 +1307,9 @@
 					self.get_gl_dict(
 						{
 							"account": payment_account,
+							"against_type": against_type,
 							"against": against,
+							"against_link": against,
 							rev_dr_or_cr: tax_amount,
 							rev_dr_or_cr + "_in_account_currency": base_tax_amount
 							if account_currency == self.company_currency
@@ -1310,7 +1334,9 @@
 						{
 							"account": d.account,
 							"account_currency": account_currency,
+							"against_type": self.party_type or "Account",
 							"against": self.party or self.paid_from,
+							"against_link": self.party or self.paid_from,
 							"debit_in_account_currency": d.amount,
 							"debit": d.amount,
 							"cost_center": d.cost_center,
@@ -1745,7 +1771,7 @@
 		"Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date"
 	)
 	for payment_term in payment_schedule:
-		if not payment_term.outstanding > 0.1:
+		if payment_term.outstanding <= 0.1:
 			continue
 
 		doc_details = exc_rates.get(payment_term.parent, None)
@@ -1980,18 +2006,24 @@
 
 
 def get_outstanding_on_journal_entry(name):
-	res = frappe.db.sql(
-		"SELECT "
-		'CASE WHEN party_type IN ("Customer") '
-		"THEN ifnull(sum(debit_in_account_currency - credit_in_account_currency), 0) "
-		"ELSE ifnull(sum(credit_in_account_currency - debit_in_account_currency), 0) "
-		"END as outstanding_amount "
-		"FROM `tabGL Entry` WHERE (voucher_no=%s OR against_voucher=%s) "
-		"AND party_type IS NOT NULL "
-		'AND party_type != ""',
-		(name, name),
-		as_dict=1,
-	)
+	gl = frappe.qb.DocType("GL Entry")
+	res = (
+		frappe.qb.from_(gl)
+		.select(
+			Case()
+			.when(
+				gl.party_type == "Customer",
+				Coalesce(Sum(gl.debit_in_account_currency - gl.credit_in_account_currency), 0),
+			)
+			.else_(Coalesce(Sum(gl.credit_in_account_currency - gl.debit_in_account_currency), 0))
+			.as_("outstanding_amount")
+		)
+		.where(
+			(Coalesce(gl.party_type, "") != "")
+			& (gl.is_cancelled == 0)
+			& ((gl.voucher_no == name) | (gl.against_voucher == name))
+		)
+	).run(as_dict=True)
 
 	outstanding_amount = res[0].get("outstanding_amount", 0) if res else 0
 
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index fbc4d24..ed0921b 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -594,6 +594,27 @@
 
 			invoice_exchange_map.update(purchase_invoice_map)
 
+		journals = [
+			d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"
+		]
+		journals.extend(
+			[d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"]
+		)
+		if journals:
+			journals = list(set(journals))
+			journals_map = frappe._dict(
+				frappe.db.get_all(
+					"Journal Entry Account",
+					filters={"parent": ("in", journals), "account": ("in", [self.receivable_payable_account])},
+					fields=[
+						"parent as `name`",
+						"exchange_rate",
+					],
+					as_list=1,
+				)
+			)
+			invoice_exchange_map.update(journals_map)
+
 		return invoice_exchange_map
 
 	def validate_allocation(self):
diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
index a4141af..b57ebec 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
@@ -34,4 +34,6 @@
 		unreconciled_amount: DF.Currency
 	# end: auto-generated types
 
-	pass
+	@staticmethod
+	def get_list(args):
+		pass
diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py
index 1e9f6ce..fa18ccd 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py
+++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py
@@ -26,4 +26,6 @@
 		parenttype: DF.Data
 	# end: auto-generated types
 
-	pass
+	@staticmethod
+	def get_list(args):
+		pass
diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py
index aa956fe..4ab80ec 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py
+++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py
@@ -30,4 +30,6 @@
 		remark: DF.SmallText | None
 	# end: auto-generated types
 
-	pass
+	@staticmethod
+	def get_list(args):
+		pass
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index bfb51da..e542d3c 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -765,7 +765,7 @@
 	reserved_qty = (
 		frappe.qb.from_(p_inv)
 		.from_(p_item)
-		.select(Sum(p_item.qty).as_("qty"))
+		.select(Sum(p_item.stock_qty).as_("stock_qty"))
 		.where(
 			(p_inv.name == p_item.parent)
 			& (IfNull(p_inv.consolidated_invoice, "") == "")
@@ -775,7 +775,7 @@
 		)
 	).run(as_dict=True)
 
-	return reserved_qty[0].qty or 0 if reserved_qty else 0
+	return flt(reserved_qty[0].stock_qty) if reserved_qty else 0
 
 
 @frappe.whitelist()
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index b41cf53..82bd662 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -286,7 +286,7 @@
 	def validate_price_list_with_currency(self):
 		if self.currency and self.for_price_list:
 			price_list_currency = frappe.db.get_value("Price List", self.for_price_list, "currency", True)
-			if not self.currency == price_list_currency:
+			if self.currency != price_list_currency:
 				throw(_("Currency should be same as Price List Currency: {0}").format(price_list_currency))
 
 	def validate_dates(self):
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 57feaa0..18aa682 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -581,6 +581,8 @@
 			if d.price_or_product_discount == "Price":
 				if d.apply_discount_on:
 					doc.set("apply_discount_on", d.apply_discount_on)
+				# Variable to track whether the condition has been met
+				condition_met = False
 
 				for field in ["additional_discount_percentage", "discount_amount"]:
 					pr_field = "discount_percentage" if field == "additional_discount_percentage" else field
@@ -603,6 +605,11 @@
 							if coupon_code_pricing_rule == d.name:
 								# if selected coupon code is linked with pricing rule
 								doc.set(field, d.get(pr_field))
+
+								# Set the condition_met variable to True and break out of the loop
+								condition_met = True
+								break
+
 							else:
 								# reset discount if not linked
 								doc.set(field, 0)
@@ -611,6 +618,10 @@
 							doc.set(field, 0)
 
 				doc.calculate_taxes_and_totals()
+
+				# Break out of the main loop if the condition is met
+				if condition_met:
+					break
 			elif d.price_or_product_discount == "Product":
 				item_details = frappe._dict({"parenttype": doc.doctype, "free_item_data": []})
 				get_product_discount_rule(d, item_details, doc=doc)
diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py
index 67a7f90..f44b14c 100644
--- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py
@@ -475,7 +475,7 @@
 						frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed")
 					else:
 
-						if not (frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"):
+						if frappe.db.get_value("Process Payment Reconciliation", doc, "status") != "Paused":
 							# trigger next batch in job
 							# generate reconcile job name
 							allocation = get_next_allocation(log)
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
index a3a953f..ae6059c 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
@@ -15,6 +15,7 @@
   "group_by",
   "cost_center",
   "territory",
+  "ignore_exchange_rate_revaluation_journals",
   "column_break_14",
   "to_date",
   "finance_book",
@@ -376,10 +377,16 @@
    "fieldname": "pdf_name",
    "fieldtype": "Data",
    "label": "PDF Name"
+  },
+  {
+   "default": "0",
+   "fieldname": "ignore_exchange_rate_revaluation_journals",
+   "fieldtype": "Check",
+   "label": "Ignore Exchange Rate Revaluation Journals"
   }
  ],
  "links": [],
- "modified": "2023-08-28 12:59:53.071334",
+ "modified": "2023-12-18 12:20:08.965120",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Process Statement Of Accounts",
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
index 9ad2548..c03b18a 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
@@ -56,6 +56,7 @@
 		frequency: DF.Literal["Weekly", "Monthly", "Quarterly"]
 		from_date: DF.Date | None
 		group_by: DF.Literal["", "Group by Voucher", "Group by Voucher (Consolidated)"]
+		ignore_exchange_rate_revaluation_journals: DF.Check
 		include_ageing: DF.Check
 		include_break: DF.Check
 		letter_head: DF.Link | None
@@ -119,6 +120,18 @@
 	statement_dict = {}
 	ageing = ""
 
+	err_journals = None
+	if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals:
+		err_journals = frappe.db.get_all(
+			"Journal Entry",
+			filters={
+				"company": doc.company,
+				"docstatus": 1,
+				"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
+			},
+			as_list=True,
+		)
+
 	for entry in doc.customers:
 		if doc.include_ageing:
 			ageing = set_ageing(doc, entry)
@@ -131,6 +144,8 @@
 		)
 
 		filters = get_common_filters(doc)
+		if err_journals:
+			filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
 
 		if doc.report == "General Ledger":
 			filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 4b0df12..215d8ec 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -35,7 +35,7 @@
 		super.onload();
 
 		// Ignore linked advances
-		this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger"];
+		this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
 
 		if(!this.frm.doc.__islocal) {
 			// show credit_to in print format
@@ -163,6 +163,18 @@
 					}
 				})
 			}, __("Get Items From"));
+
+			if (!this.frm.doc.is_return) {
+				frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
+					if (value) {
+						this.frm.doc.items.forEach((item) => {
+							this.frm.fields_dict.items.grid.update_docfield_property(
+								"rate", "read_only", (item.purchase_receipt && item.pr_detail)
+							);
+						});
+					}
+				});
+			}
 		}
 		this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);
 
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index f9c9fb5..f40824d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -371,7 +371,7 @@
 		check_list = []
 
 		for d in self.get("items"):
-			if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt:
+			if d.purchase_order and d.purchase_order not in check_list and not d.purchase_receipt:
 				check_list.append(d.purchase_order)
 				check_on_hold_or_closed_status("Purchase Order", d.purchase_order)
 
@@ -815,7 +815,9 @@
 						"party_type": "Supplier",
 						"party": self.supplier,
 						"due_date": self.due_date,
+						"against_type": "Account",
 						"against": self.against_expense_account,
+						"against_link": self.against_expense_account,
 						"credit": base_grand_total,
 						"credit_in_account_currency": base_grand_total
 						if self.party_account_currency == self.company_currency
@@ -888,7 +890,9 @@
 							self.get_gl_dict(
 								{
 									"account": warehouse_account[item.warehouse]["account"],
+									"against_type": "Account",
 									"against": warehouse_account[item.from_warehouse]["account"],
+									"against_link": warehouse_account[item.from_warehouse]["account"],
 									"cost_center": item.cost_center,
 									"project": item.project or self.project,
 									"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@@ -908,7 +912,9 @@
 							self.get_gl_dict(
 								{
 									"account": warehouse_account[item.from_warehouse]["account"],
+									"against_type": "Account",
 									"against": warehouse_account[item.warehouse]["account"],
+									"against_link": warehouse_account[item.warehouse]["account"],
 									"cost_center": item.cost_center,
 									"project": item.project or self.project,
 									"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@@ -925,7 +931,9 @@
 								self.get_gl_dict(
 									{
 										"account": item.expense_account,
+										"against_type": "Supplier",
 										"against": self.supplier,
+										"against_link": self.supplier,
 										"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
 										"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
 										"cost_center": item.cost_center,
@@ -942,7 +950,9 @@
 								self.get_gl_dict(
 									{
 										"account": item.expense_account,
+										"against_type": "Supplier",
 										"against": self.supplier,
+										"against_link": self.supplier,
 										"debit": warehouse_debit_amount,
 										"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
 										"cost_center": item.cost_center,
@@ -961,7 +971,9 @@
 									self.get_gl_dict(
 										{
 											"account": account,
+											"against_type": "Account",
 											"against": item.expense_account,
+											"against_link": item.expense_account,
 											"cost_center": item.cost_center,
 											"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
 											"credit": flt(amount["base_amount"]),
@@ -981,7 +993,9 @@
 							self.get_gl_dict(
 								{
 									"account": supplier_warehouse_account,
+									"against_type": "Account",
 									"against": item.expense_account,
+									"against_link": item.expense_account,
 									"cost_center": item.cost_center,
 									"project": item.project or self.project,
 									"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@@ -1036,7 +1050,9 @@
 							self.get_gl_dict(
 								{
 									"account": expense_account,
+									"against_type": "Supplier",
 									"against": self.supplier,
+									"against_link": self.supplier,
 									"debit": amount,
 									"cost_center": item.cost_center,
 									"project": item.project or self.project,
@@ -1062,7 +1078,9 @@
 									self.get_gl_dict(
 										{
 											"account": expense_account,
+											"against_type": "Supplier",
 											"against": self.supplier,
+											"against_link": self.supplier,
 											"debit": discrepancy_caused_by_exchange_rate_difference,
 											"cost_center": item.cost_center,
 											"project": item.project or self.project,
@@ -1075,7 +1093,9 @@
 									self.get_gl_dict(
 										{
 											"account": self.get_company_default("exchange_gain_loss_account"),
+											"against_type": "Supplier",
 											"against": self.supplier,
+											"against_link": self.supplier,
 											"credit": discrepancy_caused_by_exchange_rate_difference,
 											"cost_center": item.cost_center,
 											"project": item.project or self.project,
@@ -1120,7 +1140,9 @@
 							self.get_gl_dict(
 								{
 									"account": stock_rbnb,
+									"against_type": "Supplier",
 									"against": self.supplier,
+									"against_link": self.supplier,
 									"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
 									"remarks": self.remarks or _("Accounting Entry for Stock"),
 									"cost_center": self.cost_center,
@@ -1168,7 +1190,9 @@
 				self.get_gl_dict(
 					{
 						"account": cost_of_goods_sold_account,
+						"against_type": "Account",
 						"against": item.expense_account,
+						"against_link": item.expense_account,
 						"debit": stock_adjustment_amt,
 						"remarks": self.get("remarks") or _("Stock Adjustment"),
 						"cost_center": item.cost_center,
@@ -1198,7 +1222,9 @@
 					self.get_gl_dict(
 						{
 							"account": tax.account_head,
+							"against_type": "Supplier",
 							"against": self.supplier,
+							"against_link": self.supplier,
 							dr_or_cr: base_amount,
 							dr_or_cr + "_in_account_currency": base_amount
 							if account_currency == self.company_currency
@@ -1246,8 +1272,10 @@
 						self.get_gl_dict(
 							{
 								"account": tax.account_head,
+								"against_type": "Supplier",
 								"cost_center": tax.cost_center,
 								"against": self.supplier,
+								"against_link": self.supplier,
 								"credit": applicable_amount,
 								"remarks": self.remarks or _("Accounting Entry for Stock"),
 							},
@@ -1265,7 +1293,9 @@
 							{
 								"account": tax.account_head,
 								"cost_center": tax.cost_center,
+								"against_type": "Supplier",
 								"against": self.supplier,
+								"against_link": self.supplier,
 								"credit": valuation_tax[tax.name],
 								"remarks": self.remarks or _("Accounting Entry for Stock"),
 							},
@@ -1280,7 +1310,9 @@
 				self.get_gl_dict(
 					{
 						"account": self.unrealized_profit_loss_account,
+						"against_type": "Supplier",
 						"against": self.supplier,
+						"against_link": self.supplier,
 						"credit": flt(self.total_taxes_and_charges),
 						"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
 						"cost_center": self.cost_center,
@@ -1301,7 +1333,9 @@
 						"account": self.credit_to,
 						"party_type": "Supplier",
 						"party": self.supplier,
+						"against_type": "Account",
 						"against": self.cash_bank_account,
+						"against_link": self.cash_bank_account,
 						"debit": self.base_paid_amount,
 						"debit_in_account_currency": self.base_paid_amount
 						if self.party_account_currency == self.company_currency
@@ -1322,7 +1356,9 @@
 				self.get_gl_dict(
 					{
 						"account": self.cash_bank_account,
+						"against_type": "Supplier",
 						"against": self.supplier,
+						"against_link": self.supplier,
 						"credit": self.base_paid_amount,
 						"credit_in_account_currency": self.base_paid_amount
 						if bank_account_currency == self.company_currency
@@ -1346,7 +1382,9 @@
 						"account": self.credit_to,
 						"party_type": "Supplier",
 						"party": self.supplier,
+						"against_type": "Account",
 						"against": self.write_off_account,
+						"against_link": self.write_off_account,
 						"debit": self.base_write_off_amount,
 						"debit_in_account_currency": self.base_write_off_amount
 						if self.party_account_currency == self.company_currency
@@ -1366,7 +1404,9 @@
 				self.get_gl_dict(
 					{
 						"account": self.write_off_account,
+						"against_type": "Supplier",
 						"against": self.supplier,
+						"against_link": self.supplier,
 						"credit": flt(self.base_write_off_amount),
 						"credit_in_account_currency": self.base_write_off_amount
 						if write_off_account_currency == self.company_currency
@@ -1393,7 +1433,9 @@
 				self.get_gl_dict(
 					{
 						"account": round_off_account,
+						"against_type": "Supplier",
 						"against": self.supplier,
+						"against_link": self.supplier,
 						"debit_in_account_currency": self.rounding_adjustment,
 						"debit": self.base_rounding_adjustment,
 						"cost_center": round_off_cost_center
@@ -1449,6 +1491,8 @@
 			"Repost Payment Ledger Items",
 			"Repost Accounting Ledger",
 			"Repost Accounting Ledger Items",
+			"Unreconcile Payment",
+			"Unreconcile Payment Entries",
 			"Payment Ledger Entry",
 			"Tax Withheld Vouchers",
 			"Serial and Batch Bundle",
@@ -1814,10 +1858,6 @@
 	return make_inter_company_transaction("Purchase Invoice", source_name, target_doc)
 
 
-def on_doctype_update():
-	frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
-
-
 @frappe.whitelist()
 def make_purchase_receipt(source_name, target_doc=None):
 	def update_item(obj, target, source_parent):
@@ -1859,6 +1899,4 @@
 		target_doc,
 	)
 
-	doc.set_onload("ignore_price_list", True)
-
 	return doc
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 7cad3ae..9cf4e4f 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -288,7 +288,6 @@
    "oldfieldname": "import_rate",
    "oldfieldtype": "Currency",
    "options": "currency",
-   "read_only_depends_on": "eval: (!parent.is_return && doc.purchase_receipt && doc.pr_detail)",
    "reqd": 1
   },
   {
@@ -919,7 +918,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-30 16:26:05.629780",
+ "modified": "2023-12-25 22:00:28.043555",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
index 8c23c67..7aa631b 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
@@ -126,7 +126,7 @@
 		return rendered_page
 
 	def on_submit(self):
-		if len(self.vouchers) > 1:
+		if len(self.vouchers) > 5:
 			job_name = "repost_accounting_ledger_" + self.name
 			frappe.enqueue(
 				method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
@@ -170,8 +170,6 @@
 						doc.make_gl_entries(1)
 					doc.make_gl_entries()
 
-				frappe.db.commit()
-
 
 def get_allowed_types_from_settings():
 	return [
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
index dda0ec7..d6f7096 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
@@ -20,18 +20,11 @@
 		self.create_company()
 		self.create_customer()
 		self.create_item()
-		self.update_repost_settings()
+		update_repost_settings()
 
-	def teadDown(self):
+	def tearDown(self):
 		frappe.db.rollback()
 
-	def update_repost_settings(self):
-		allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
-		repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
-		for x in allowed_types:
-			repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
-			repost_settings.save()
-
 	def test_01_basic_functions(self):
 		si = create_sales_invoice(
 			item=self.item,
@@ -90,9 +83,6 @@
 		# Submit repost document
 		ral.save().submit()
 
-		# background jobs don't run on test cases. Manually triggering repost function.
-		start_repost(ral.name)
-
 		res = (
 			qb.from_(gl)
 			.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
@@ -177,26 +167,6 @@
 		pe = get_payment_entry(si.doctype, si.name)
 		pe.save().submit()
 
-		# without deletion flag set
-		ral = frappe.new_doc("Repost Accounting Ledger")
-		ral.company = self.company
-		ral.delete_cancelled_entries = False
-		ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
-		ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
-		ral.save()
-
-		# assert preview data is generated
-		preview = ral.generate_preview()
-		self.assertIsNotNone(preview)
-
-		ral.save().submit()
-
-		# background jobs don't run on test cases. Manually triggering repost function.
-		start_repost(ral.name)
-
-		self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
-		self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
-
 		# with deletion flag set
 		ral = frappe.new_doc("Repost Accounting Ledger")
 		ral.company = self.company
@@ -205,6 +175,38 @@
 		ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
 		ral.save().submit()
 
-		start_repost(ral.name)
 		self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
 		self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
+
+	def test_05_without_deletion_flag(self):
+		si = create_sales_invoice(
+			item=self.item,
+			company=self.company,
+			customer=self.customer,
+			debit_to=self.debit_to,
+			parent_cost_center=self.cost_center,
+			cost_center=self.cost_center,
+			rate=100,
+		)
+
+		pe = get_payment_entry(si.doctype, si.name)
+		pe.save().submit()
+
+		# without deletion flag set
+		ral = frappe.new_doc("Repost Accounting Ledger")
+		ral.company = self.company
+		ral.delete_cancelled_entries = False
+		ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
+		ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
+		ral.save().submit()
+
+		self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
+		self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
+
+
+def update_repost_settings():
+	allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
+	repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
+	for x in allowed_types:
+		repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
+		repost_settings.save()
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 0ebf335..2cddb86 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -7,7 +7,7 @@
 from frappe.contacts.doctype.address.address import get_address_display
 from frappe.model.mapper import get_mapped_doc
 from frappe.model.utils import get_fetch_values
-from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
+from frappe.utils import add_days, cint, flt, formatdate, get_link_to_form, getdate, nowdate
 
 import erpnext
 from erpnext.accounts.deferred_revenue import validate_service_stop_date
@@ -458,7 +458,7 @@
 			self.update_billing_status_for_zero_amount_refdoc("Sales Order")
 			self.check_credit_limit()
 
-		if not cint(self.is_pos) == 1 and not self.is_return:
+		if cint(self.is_pos) != 1 and not self.is_return:
 			self.update_against_document_in_jv()
 
 		self.update_time_sheet(self.name)
@@ -1225,7 +1225,9 @@
 						"party_type": "Customer",
 						"party": self.customer,
 						"due_date": self.due_date,
+						"against_type": "Account",
 						"against": self.against_income_account,
+						"against_link": self.against_income_account,
 						"debit": base_grand_total,
 						"debit_in_account_currency": base_grand_total
 						if self.party_account_currency == self.company_currency
@@ -1254,7 +1256,9 @@
 					self.get_gl_dict(
 						{
 							"account": tax.account_head,
+							"against_type": "Customer",
 							"against": self.customer,
+							"against_link": self.customer,
 							"credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")),
 							"credit_in_account_currency": (
 								flt(base_amount, tax.precision("base_tax_amount_after_discount_amount"))
@@ -1275,7 +1279,9 @@
 				self.get_gl_dict(
 					{
 						"account": self.unrealized_profit_loss_account,
+						"against_type": "Customer",
 						"against": self.customer,
+						"against_link": self.customer,
 						"debit": flt(self.total_taxes_and_charges),
 						"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
 						"cost_center": self.cost_center,
@@ -1343,7 +1349,9 @@
 						add_asset_activity(asset.name, _("Asset sold"))
 
 					for gle in fixed_asset_gl_entries:
+						gle["against_type"] = "Customer"
 						gle["against"] = self.customer
+						gle["against_link"] = self.customer
 						gl_entries.append(self.get_gl_dict(gle, item=item))
 
 					self.set_asset_status(asset)
@@ -1364,7 +1372,9 @@
 							self.get_gl_dict(
 								{
 									"account": income_account,
+									"against_type": "Customer",
 									"against": self.customer,
+									"against_link": self.customer,
 									"credit": flt(base_amount, item.precision("base_net_amount")),
 									"credit_in_account_currency": (
 										flt(base_amount, item.precision("base_net_amount"))
@@ -1418,9 +1428,9 @@
 						"account": self.debit_to,
 						"party_type": "Customer",
 						"party": self.customer,
-						"against": "Expense account - "
-						+ cstr(self.loyalty_redemption_account)
-						+ " for the Loyalty Program",
+						"against_type": "Account",
+						"against": self.loyalty_redemption_account,
+						"against_link": self.loyalty_redemption_account,
 						"credit": self.loyalty_amount,
 						"against_voucher": self.return_against if cint(self.is_return) else self.name,
 						"against_voucher_type": self.doctype,
@@ -1434,7 +1444,9 @@
 					{
 						"account": self.loyalty_redemption_account,
 						"cost_center": self.cost_center or self.loyalty_redemption_cost_center,
+						"against_type": "Customer",
 						"against": self.customer,
+						"against_link": self.customer,
 						"debit": self.loyalty_amount,
 						"remark": "Loyalty Points redeemed by the customer",
 					},
@@ -1461,7 +1473,9 @@
 								"account": self.debit_to,
 								"party_type": "Customer",
 								"party": self.customer,
+								"against_type": "Account",
 								"against": payment_mode.account,
+								"against_link": payment_mode.account,
 								"credit": payment_mode.base_amount,
 								"credit_in_account_currency": payment_mode.base_amount
 								if self.party_account_currency == self.company_currency
@@ -1482,7 +1496,9 @@
 						self.get_gl_dict(
 							{
 								"account": payment_mode.account,
+								"against_type": "Customer",
 								"against": self.customer,
+								"against_link": self.customer,
 								"debit": payment_mode.base_amount,
 								"debit_in_account_currency": payment_mode.base_amount
 								if payment_mode_account_currency == self.company_currency
@@ -1506,7 +1522,9 @@
 							"account": self.debit_to,
 							"party_type": "Customer",
 							"party": self.customer,
+							"against_type": "Account",
 							"against": self.account_for_change_amount,
+							"against_link": self.account_for_change_amount,
 							"debit": flt(self.base_change_amount),
 							"debit_in_account_currency": flt(self.base_change_amount)
 							if self.party_account_currency == self.company_currency
@@ -1527,7 +1545,9 @@
 					self.get_gl_dict(
 						{
 							"account": self.account_for_change_amount,
+							"against_type": "Customer",
 							"against": self.customer,
+							"against_link": self.customer,
 							"credit": self.base_change_amount,
 							"cost_center": self.cost_center,
 						},
@@ -1553,7 +1573,9 @@
 						"account": self.debit_to,
 						"party_type": "Customer",
 						"party": self.customer,
+						"against_type": "Account",
 						"against": self.write_off_account,
+						"against_link": self.write_off_account,
 						"credit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
 						"credit_in_account_currency": (
 							flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
@@ -1573,7 +1595,9 @@
 				self.get_gl_dict(
 					{
 						"account": self.write_off_account,
+						"against_type": "Customer",
 						"against": self.customer,
+						"against_link": self.customer,
 						"debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
 						"debit_in_account_currency": (
 							flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
@@ -1601,7 +1625,9 @@
 				self.get_gl_dict(
 					{
 						"account": round_off_account,
+						"against_type": "Customer",
 						"against": self.customer,
+						"against_link": self.customer,
 						"credit_in_account_currency": flt(
 							self.rounding_adjustment, self.precision("rounding_adjustment")
 						),
@@ -1960,9 +1986,9 @@
 	if inter_company_reference:
 		doc = frappe.get_doc(ref_doc, inter_company_reference)
 		ref_party = doc.supplier if doctype in ["Sales Invoice", "Sales Order"] else doc.customer
-		if not frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") == party:
+		if frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") != party:
 			frappe.throw(_("Invalid {0} for Inter Company Transaction.").format(_(partytype)))
-		if not frappe.get_cached_value(ref_partytype, ref_party, "represents_company") == company:
+		if frappe.get_cached_value(ref_partytype, ref_party, "represents_company") != company:
 			frappe.throw(_("Invalid Company for Inter Company Transaction."))
 
 	elif frappe.db.get_value(partytype, {"name": party, internal: 1}, "name") == party:
@@ -1972,7 +1998,7 @@
 			filters={"parenttype": partytype, "parent": party},
 		)
 		companies = [d.company for d in companies]
-		if not company in companies:
+		if company not in companies:
 			frappe.throw(
 				_("{0} not allowed to transact with {1}. Please change the Company.").format(
 					_(partytype), company
@@ -2095,7 +2121,6 @@
 		set_missing_values,
 	)
 
-	doclist.set_onload("ignore_price_list", True)
 	return doclist
 
 
@@ -2357,9 +2382,18 @@
 
 
 def get_received_items(reference_name, doctype, reference_fieldname):
+	reference_field = "inter_company_invoice_reference"
+	if doctype == "Purchase Order":
+		reference_field = "inter_company_order_reference"
+
+	filters = {
+		reference_field: reference_name,
+		"docstatus": 1,
+	}
+
 	target_doctypes = frappe.get_all(
 		doctype,
-		filters={"inter_company_invoice_reference": reference_name, "docstatus": 1},
+		filters=filters,
 		as_list=True,
 	)
 
@@ -2541,10 +2575,6 @@
 		return lp_details
 
 
-def on_doctype_update():
-	frappe.db.add_index("Sales Invoice", ["customer", "is_return", "return_against"])
-
-
 @frappe.whitelist()
 def create_invoice_discounting(source_name, target_doc=None):
 	invoice = frappe.get_doc("Sales Invoice", source_name)
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 840a319..6163749 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2629,6 +2629,7 @@
 					"voucher_type": "Sales Invoice",
 					"voucher_no": si.name,
 					"allow_zero_valuation": d.get("allow_zero_valuation"),
+					"voucher_detail_no": d.name,
 				},
 				raise_error_if_no_rate=False,
 			)
@@ -2792,6 +2793,12 @@
 	@change_settings("Selling Settings", {"enable_discount_accounting": 1})
 	def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
 
+		from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
+			update_repost_settings,
+		)
+
+		update_repost_settings()
+
 		additional_discount_account = create_account(
 			account_name="Discount Account",
 			parent_account="Indirect Expenses - _TC",
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
index 52a60ac..6877a74 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
@@ -142,12 +142,12 @@
 		}
 		if self.shipping_rule_type == "Selling":
 			# check if not applied on purchase
-			if not doc.meta.get_field("taxes").options == "Sales Taxes and Charges":
+			if doc.meta.get_field("taxes").options != "Sales Taxes and Charges":
 				frappe.throw(_("Shipping rule only applicable for Selling"))
 			shipping_charge["doctype"] = "Sales Taxes and Charges"
 		else:
 			# check if not applied on sales
-			if not doc.meta.get_field("taxes").options == "Purchase Taxes and Charges":
+			if doc.meta.get_field("taxes").options != "Purchase Taxes and Charges":
 				frappe.throw(_("Shipping rule only applicable for Buying"))
 
 			shipping_charge["doctype"] = "Purchase Taxes and Charges"
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 6cc2d1e..aba1b64 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -676,7 +676,7 @@
 		to_generate_invoice = (
 			True
 			if self.status == "Active"
-			and not self.generate_invoice_at == "Beginning of the current subscription period"
+			and self.generate_invoice_at != "Beginning of the current subscription period"
 			else False
 		)
 		self.status = "Cancelled"
@@ -694,7 +694,7 @@
 		subscription and the `Subscription` will lose all the history of generated invoices
 		it has.
 		"""
-		if not self.status == "Cancelled":
+		if self.status != "Cancelled":
 			frappe.throw(_("You cannot restart a Subscription that is not cancelled."), InvoiceNotCancelled)
 
 		self.status = "Active"
diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
index e258a73..9b56952 100644
--- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
+++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
@@ -37,7 +37,7 @@
 
 	def validate(self):
 		self.supported_types = ["Payment Entry", "Journal Entry"]
-		if not self.voucher_type in self.supported_types:
+		if self.voucher_type not in self.supported_types:
 			frappe.throw(_("Only {0} are supported").format(comma_and(self.supported_types)))
 
 	@frappe.whitelist()
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 030a41e..91ba239 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -280,6 +280,7 @@
 		"project",
 		"finance_book",
 		"voucher_no",
+		"against_link",
 	]
 
 	if dimensions:
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 5c18e50..008614e 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -195,7 +195,7 @@
 	company_address=None,
 	shipping_address=None,
 	*,
-	ignore_permissions=False
+	ignore_permissions=False,
 ):
 	billing_address_field = (
 		"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
@@ -239,7 +239,7 @@
 				shipping_address_display=render_address(
 					shipping_address, check_permissions=not ignore_permissions
 				),
-				**get_fetch_values(doctype, "shipping_address", shipping_address)
+				**get_fetch_values(doctype, "shipping_address", shipping_address),
 			)
 
 		if party_details.company_address:
@@ -250,7 +250,7 @@
 					party_details.company_address_display
 					or render_address(party_details.company_address, check_permissions=False)
 				),
-				**get_fetch_values(doctype, "billing_address", party_details.company_address)
+				**get_fetch_values(doctype, "billing_address", party_details.company_address),
 			)
 
 			# shipping address - if not already set
@@ -258,7 +258,7 @@
 				party_details.update(
 					shipping_address=party_details.billing_address,
 					shipping_address_display=party_details.billing_address_display,
-					**get_fetch_values(doctype, "shipping_address", party_details.billing_address)
+					**get_fetch_values(doctype, "shipping_address", party_details.billing_address),
 				)
 
 	party_address, shipping_address = (
@@ -775,7 +775,7 @@
 				frozen_accounts_modifier = frappe.db.get_single_value(
 					"Accounts Settings", "frozen_accounts_modifier"
 				)
-				if not frozen_accounts_modifier in frappe.get_roles():
+				if frozen_accounts_modifier not in frappe.get_roles():
 					frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
 
 		elif party_type == "Employee":
@@ -981,6 +981,9 @@
 	if party:
 		query = query.where(ple.party == party)
 
+	if invoice_doctypes := frappe.get_hooks("invoice_doctypes"):
+		query = query.where(ple.voucher_type.notin(invoice_doctypes))
+
 	data = query.run()
 	if data:
 		return frappe._dict(data)
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 7948e5f..9feda11 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -123,7 +123,7 @@
 			else:
 				key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
 
-			if not key in self.voucher_balance:
+			if key not in self.voucher_balance:
 				self.voucher_balance[key] = frappe._dict(
 					voucher_type=ple.voucher_type,
 					voucher_no=ple.voucher_no,
@@ -225,7 +225,7 @@
 		if not row:
 			return
 
-		if self.filters.get("in_party_currency"):
+		if self.filters.get("in_party_currency") or self.filters.get("party_account"):
 			amount = ple.amount_in_account_currency
 		else:
 			amount = ple.amount
@@ -244,8 +244,12 @@
 				row.invoiced_in_account_currency += amount_in_account_currency
 		else:
 			if self.is_invoice(ple):
-				row.credit_note -= amount
-				row.credit_note_in_account_currency -= amount_in_account_currency
+				if row.voucher_no == ple.voucher_no == ple.against_voucher_no:
+					row.paid -= amount
+					row.paid_in_account_currency -= amount_in_account_currency
+				else:
+					row.credit_note -= amount
+					row.credit_note_in_account_currency -= amount_in_account_currency
 			else:
 				row.paid -= amount
 				row.paid_in_account_currency -= amount_in_account_currency
@@ -451,7 +455,7 @@
 		party_details = self.get_party_details(row.party) or {}
 		row.update(party_details)
 
-		if self.filters.get("in_party_currency"):
+		if self.filters.get("in_party_currency") or self.filters.get("party_account"):
 			row.currency = row.account_currency
 		else:
 			row.currency = self.company_currency
@@ -938,7 +942,7 @@
 			return True
 
 	def get_party_details(self, party):
-		if not party in self.party_details:
+		if party not in self.party_details:
 			if self.account_type == "Receivable":
 				fields = ["customer_name", "territory", "customer_group", "customer_primary_contact"]
 
@@ -1087,7 +1091,7 @@
 			)
 
 		if self.filters.show_remarks:
-			self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
+			self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200)
 
 	def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
 		if not fieldname:
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index dd0842d..976935b 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -76,6 +76,41 @@
 
 		return credit_note
 
+	def test_pos_receivable(self):
+		filters = {
+			"company": self.company,
+			"party_type": "Customer",
+			"party": [self.customer],
+			"report_date": add_days(today(), 2),
+			"based_on_payment_terms": 0,
+			"range1": 30,
+			"range2": 60,
+			"range3": 90,
+			"range4": 120,
+			"show_remarks": False,
+		}
+
+		pos_inv = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
+		pos_inv.posting_date = add_days(today(), 2)
+		pos_inv.is_pos = 1
+		pos_inv.append(
+			"payments",
+			frappe._dict(
+				mode_of_payment="Cash",
+				amount=flt(pos_inv.grand_total / 2),
+			),
+		)
+		pos_inv.disable_rounded_total = 1
+		pos_inv.save()
+		pos_inv.submit()
+
+		report = execute(filters)
+		expected_data = [[pos_inv.grand_total, pos_inv.paid_amount, 0]]
+
+		row = report[1][-1]
+		self.assertEqual(expected_data[0], [row.invoiced, row.paid, row.credit_note])
+		pos_inv.cancel()
+
 	def test_accounts_receivable(self):
 		filters = {
 			"company": self.company,
@@ -544,7 +579,7 @@
 		filters.update({"party_account": self.debtors_usd})
 		report = execute(filters)[1]
 		self.assertEqual(len(report), 1)
-		expected_data = [8000.0, 8000.0, self.debtors_usd, si2.currency]
+		expected_data = [100.0, 100.0, self.debtors_usd, si2.currency]
 		row = report[0]
 		self.assertEqual(
 			expected_data, [row.invoiced, row.outstanding, row.party_account, row.account_currency]
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
index cad5325..eebd61c 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
@@ -97,7 +97,7 @@
 		if base_amount + already_booked_amount > self.base_net_amount:
 			base_amount = self.base_net_amount - already_booked_amount
 
-		if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
+		if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date:
 			partial_month = flt(date_diff(end_date, start_date)) / flt(
 				date_diff(get_last_day(end_date), get_first_day(start_date))
 			)
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 096bb10..7355c4b 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -8,7 +8,17 @@
 
 import frappe
 from frappe import _
-from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate
+from frappe.utils import (
+	add_days,
+	add_months,
+	cint,
+	cstr,
+	flt,
+	formatdate,
+	get_first_day,
+	getdate,
+	today,
+)
 
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_accounting_dimensions,
@@ -43,6 +53,8 @@
 		year_start_date = getdate(period_start_date)
 		year_end_date = getdate(period_end_date)
 
+	year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date
+
 	months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
 
 	period_list = []
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index fa557a1..4054dca 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -200,10 +200,10 @@
 		"""
 		select
 			name as gl_entry, posting_date, account, party_type, party,
-			voucher_type, voucher_no, {dimension_fields}
+			voucher_type, voucher_subtype, voucher_no, {dimension_fields}
 			cost_center, project, {transaction_currency_fields}
 			against_voucher_type, against_voucher, account_currency,
-			against, is_opening, creation {select_fields}
+			against_link, against, is_opening, creation {select_fields}
 		from `tabGL Entry`
 		where company=%(company)s {conditions}
 		{order_by_statement}
@@ -238,6 +238,9 @@
 	if filters.get("voucher_no"):
 		conditions.append("voucher_no=%(voucher_no)s")
 
+	if filters.get("voucher_no_not_in"):
+		conditions.append("voucher_no not in %(voucher_no_not_in)s")
+
 	if filters.get("group_by") == "Group by Party" and not filters.get("party_type"):
 		conditions.append("party_type in ('Customer', 'Supplier')")
 
@@ -289,7 +292,8 @@
 
 	if accounting_dimensions:
 		for dimension in accounting_dimensions:
-			if not dimension.disabled:
+			# Ignore 'Finance Book' set up as dimension in below logic, as it is already handled in above section
+			if not dimension.disabled and dimension.document_type != "Finance Book":
 				if filters.get(dimension.fieldname):
 					if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
 						filters[dimension.fieldname] = get_dimension_with_children(
@@ -391,6 +395,7 @@
 	group_by = group_by_field(filters.get("group_by"))
 
 	for gle in gl_entries:
+		gle.against = gle.get("against_link") or gle.get("against")
 		gle_map.setdefault(gle.get(group_by), _dict(totals=get_totals_dict(), entries=[]))
 	return gle_map
 
@@ -605,6 +610,12 @@
 	columns += [
 		{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 120},
 		{
+			"label": _("Voucher Subtype"),
+			"fieldname": "voucher_subtype",
+			"fieldtype": "Data",
+			"width": 180,
+		},
+		{
 			"label": _("Voucher No"),
 			"fieldname": "voucher_no",
 			"fieldtype": "Dynamic Link",
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
index f0ca405..604bc01 100644
--- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
@@ -170,7 +170,7 @@
 	totals[node["account"]] += value
 
 	parent = node["parent_account"]
-	if not parent == "":
+	if parent != "":
 		return set_total(
 			next(item for item in complete_list if item["account"] == parent), value, complete_list, totals
 		)
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 38060bb..e4efefe 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -695,7 +695,7 @@
 
 	def get_average_buying_rate(self, row, item_code):
 		args = row
-		if not item_code in self.average_buying_rate:
+		if item_code not in self.average_buying_rate:
 			args.update(
 				{
 					"voucher_type": row.parenttype,
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index ad196a9..9c6e2d0 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -319,7 +319,7 @@
 			`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
 			`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
 			`tabPurchase Invoice`.unrealized_profit_loss_account,
-			`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
+			`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_group`,
 			`tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group,
 			`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
 			`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py
index 9721987..39eb312 100644
--- a/erpnext/accounts/report/purchase_register/purchase_register.py
+++ b/erpnext/accounts/report/purchase_register/purchase_register.py
@@ -89,6 +89,8 @@
 			"payable_account": inv.credit_to,
 			"mode_of_payment": inv.mode_of_payment,
 			"project": ", ".join(project) if inv.doctype == "Purchase Invoice" else inv.project,
+			"bill_no": inv.bill_no,
+			"bill_date": inv.bill_date,
 			"remarks": inv.remarks,
 			"purchase_order": ", ".join(purchase_order),
 			"purchase_receipt": ", ".join(purchase_receipt),
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 f6c7bd3..d045d91 100644
--- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
@@ -154,7 +154,7 @@
 	)
 
 	for d in gle:
-		if not d.voucher_no in gle_map:
+		if d.voucher_no not in gle_map:
 			gle_map[d.voucher_no] = [d]
 		else:
 			gle_map[d.voucher_no].append(d)
@@ -345,21 +345,16 @@
 
 	if filters.get("party"):
 		party = [filters.get("party")]
-		query = query.where(
-			((gle.account.isin(tds_accounts) & gle.against.isin(party)))
-			| ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")))
-			| gle.party.isin(party)
+		jv_condition = gle.against.isin(party) | (
+			(gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party"))
 		)
 	else:
 		party = frappe.get_all(filters.get("party_type"), pluck="name")
-		query = query.where(
-			((gle.account.isin(tds_accounts) & gle.against.isin(party)))
-			| (
-				(gle.voucher_type == "Journal Entry")
-				& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
-			)
-			| gle.party.isin(party)
+		jv_condition = gle.against.isin(party) | (
+			(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))
 	return query
 
 
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index f88e26e..adec0ab 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -642,6 +642,7 @@
 	new_row.set("reference_name", d["against_voucher"])
 
 	new_row.against_account = cstr(jv_detail.against_account)
+	new_row.against_account_link = cstr(jv_detail.against_account)
 	new_row.is_advance = cstr(jv_detail.is_advance)
 	new_row.docstatus = 1
 
@@ -662,8 +663,10 @@
 		"total_amount": d.grand_total,
 		"outstanding_amount": d.outstanding_amount,
 		"allocated_amount": d.allocated_amount,
-		"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(),
-		"exchange_gain_loss": d.exchange_gain_loss,
+		"exchange_rate": d.exchange_rate
+		if d.difference_amount is not None
+		else payment_entry.get_exchange_rate(),
+		"exchange_gain_loss": d.difference_amount,
 		"account": d.account,
 	}
 
@@ -1062,11 +1065,11 @@
 			if (
 				min_outstanding
 				and max_outstanding
-				and not (outstanding_amount >= min_outstanding and outstanding_amount <= max_outstanding)
+				and (outstanding_amount < min_outstanding or outstanding_amount > max_outstanding)
 			):
 				continue
 
-			if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices:
+			if d.voucher_type != "Purchase Invoice" or d.voucher_no not in held_invoices:
 				outstanding_invoices.append(
 					frappe._dict(
 						{
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index 540a4f5..ac712d4 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -35,6 +35,8 @@
   "purchase_receipt",
   "purchase_invoice",
   "available_for_use_date",
+  "total_asset_cost",
+  "additional_asset_cost",
   "column_break_23",
   "gross_purchase_amount",
   "asset_quantity",
@@ -529,6 +531,22 @@
    "label": "Capitalized In",
    "options": "Asset Capitalization",
    "read_only": 1
+  },
+  {
+   "depends_on": "eval:doc.docstatus > 0",
+   "fieldname": "total_asset_cost",
+   "fieldtype": "Currency",
+   "label": "Total Asset Cost",
+   "options": "Company:company:default_currency",
+   "read_only": 1
+  },
+  {
+   "depends_on": "eval:doc.docstatus > 0",
+   "fieldname": "additional_asset_cost",
+   "fieldtype": "Currency",
+   "label": "Additional Asset Cost",
+   "options": "Company:company:default_currency",
+   "read_only": 1
   }
  ],
  "idx": 72,
@@ -572,7 +590,7 @@
    "link_fieldname": "target_asset"
   }
  ],
- "modified": "2023-11-20 20:57:37.010467",
+ "modified": "2023-12-21 16:46:20.732869",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 5fb2d36..dd34189 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -50,6 +50,7 @@
 
 		from erpnext.assets.doctype.asset_finance_book.asset_finance_book import AssetFinanceBook
 
+		additional_asset_cost: DF.Currency
 		amended_from: DF.Link | None
 		asset_category: DF.Link | None
 		asset_name: DF.Data
@@ -111,6 +112,7 @@
 			"Decapitalized",
 		]
 		supplier: DF.Link | None
+		total_asset_cost: DF.Currency
 		total_number_of_depreciations: DF.Int
 		value_after_depreciation: DF.Currency
 	# end: auto-generated types
@@ -144,6 +146,7 @@
 							).format(asset_depr_schedules_links)
 						)
 
+		self.total_asset_cost = self.gross_purchase_amount
 		self.status = self.get_status()
 
 	def on_submit(self):
@@ -313,7 +316,7 @@
 			frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
 
 		if is_cwip_accounting_enabled(self.asset_category):
-			if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice):
+			if not self.is_existing_asset and not self.purchase_receipt and not self.purchase_invoice:
 				frappe.throw(
 					_("Please create purchase receipt or purchase invoice for the item {0}").format(
 						self.item_code
@@ -689,7 +692,9 @@
 				self.get_gl_dict(
 					{
 						"account": cwip_account,
+						"against_type": "Account",
 						"against": fixed_asset_account,
+						"against_link": fixed_asset_account,
 						"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
 						"posting_date": self.available_for_use_date,
 						"credit": self.purchase_receipt_amount,
@@ -704,7 +709,9 @@
 				self.get_gl_dict(
 					{
 						"account": fixed_asset_account,
+						"against_type": "Account",
 						"against": cwip_account,
+						"against_link": cwip_account,
 						"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
 						"posting_date": self.available_for_use_date,
 						"debit": self.purchase_receipt_amount,
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index dc80aa5..0773698 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -251,7 +251,16 @@
 				flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
 				0.0,
 			),
-			("_Test Fixed Asset - _TC", 0.0, 100000.0),
+			(
+				"_Test Fixed Asset - _TC",
+				0.0,
+				flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
+			),
+			(
+				"_Test Fixed Asset - _TC",
+				0.0,
+				flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")),
+			),
 			(
 				"_Test Gain/Loss on Asset Disposal - _TC",
 				flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")),
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index 66997ca..de75841 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -485,7 +485,9 @@
 						self.get_gl_dict(
 							{
 								"account": account,
+								"against_type": "Account",
 								"against": target_account,
+								"against_link": target_account,
 								"cost_center": item_row.cost_center,
 								"project": item_row.get("project") or self.get("project"),
 								"remarks": self.get("remarks") or "Accounting Entry for Stock",
@@ -526,7 +528,9 @@
 			self.set_consumed_asset_status(asset)
 
 			for gle in fixed_asset_gl_entries:
+				gle["against_type"] = "Account"
 				gle["against"] = target_account
+				gle["against_link"] = target_account
 				gl_entries.append(self.get_gl_dict(gle, item=item))
 				target_against.add(gle["account"])
 
@@ -542,7 +546,9 @@
 				self.get_gl_dict(
 					{
 						"account": item_row.expense_account,
+						"against_type": "Account",
 						"against": target_account,
+						"against_link": target_account,
 						"cost_center": item_row.cost_center,
 						"project": item_row.get("project") or self.get("project"),
 						"remarks": self.get("remarks") or "Accounting Entry for Stock",
@@ -553,41 +559,46 @@
 			)
 
 	def get_gl_entries_for_target_item(self, gl_entries, target_against, precision):
-		if self.target_is_fixed_asset:
-			# Capitalization
-			gl_entries.append(
-				self.get_gl_dict(
-					{
-						"account": self.target_fixed_asset_account,
-						"against": ", ".join(target_against),
-						"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
-						"debit": flt(self.total_value, precision),
-						"cost_center": self.get("cost_center"),
-					},
-					item=self,
-				)
-			)
-		else:
-			# Target Stock Item
-			sle_list = self.sle_map.get(self.name)
-			for sle in sle_list:
-				stock_value_difference = flt(sle.stock_value_difference, precision)
-				account = self.warehouse_account[sle.warehouse]["account"]
-
+		for target_account in target_against:
+			if self.target_is_fixed_asset:
+				# Capitalization
 				gl_entries.append(
 					self.get_gl_dict(
 						{
-							"account": account,
-							"against": ", ".join(target_against),
-							"cost_center": self.cost_center,
-							"project": self.get("project"),
-							"remarks": self.get("remarks") or "Accounting Entry for Stock",
-							"debit": stock_value_difference,
+							"account": self.target_fixed_asset_account,
+							"against_type": "Account",
+							"against": target_account,
+							"against_link": target_account,
+							"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
+							"debit": flt(self.total_value, precision) / len(target_against),
+							"cost_center": self.get("cost_center"),
 						},
-						self.warehouse_account[sle.warehouse]["account_currency"],
 						item=self,
 					)
 				)
+			else:
+				# Target Stock Item
+				sle_list = self.sle_map.get(self.name)
+				for sle in sle_list:
+					stock_value_difference = flt(sle.stock_value_difference, precision)
+					account = self.warehouse_account[sle.warehouse]["account"]
+
+					gl_entries.append(
+						self.get_gl_dict(
+							{
+								"account": account,
+								"against_type": "Account",
+								"against": target_account,
+								"against_link": target_account,
+								"cost_center": self.cost_center,
+								"project": self.get("project"),
+								"remarks": self.get("remarks") or "Accounting Entry for Stock",
+								"debit": stock_value_difference / len(target_against),
+							},
+							self.warehouse_account[sle.warehouse]["account_currency"],
+							item=self,
+						)
+					)
 
 	def create_target_asset(self):
 		if (
diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py
index ac7c90d..7a7a10d 100644
--- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py
@@ -98,12 +98,12 @@
 
 		# Test General Ledger Entries
 		expected_gle = {
-			"_Test Fixed Asset - TCP1": 3000,
+			"_Test Fixed Asset - TCP1": 2999.99,
 			"Expenses Included In Asset Valuation - TCP1": -1000,
 			"_Test Warehouse - TCP1": -2000,
+			"Round Off - TCP1": 0.01,
 		}
 		actual_gle = get_actual_gle_dict(asset_capitalization.name)
-
 		self.assertEqual(actual_gle, expected_gle)
 
 		# Test Stock Ledger Entries
@@ -189,9 +189,10 @@
 		# Test General Ledger Entries
 		default_expense_account = frappe.db.get_value("Company", company, "default_expense_account")
 		expected_gle = {
-			"_Test Fixed Asset - _TC": 3000,
+			"_Test Fixed Asset - _TC": 2999.99,
 			"Expenses Included In Asset Valuation - _TC": -1000,
 			default_expense_account: -2000,
+			"Round Off - _TC": 0.01,
 		}
 		actual_gle = get_actual_gle_dict(asset_capitalization.name)
 
@@ -376,9 +377,10 @@
 
 		# Test General Ledger Entries
 		expected_gle = {
-			"_Test Warehouse - TCP1": consumed_asset_value_before_disposal,
 			"_Test Accumulated Depreciations - TCP1": accumulated_depreciation,
 			"_Test Fixed Asset - TCP1": -consumed_asset_purchase_value,
+			"_Test Warehouse - TCP1": consumed_asset_value_before_disposal - 0.01,
+			"Round Off - TCP1": 0.01,
 		}
 		actual_gle = get_actual_gle_dict(asset_capitalization.name)
 		self.assertEqual(actual_gle, expected_gle)
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 0021140..67234cc 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -340,6 +340,10 @@
 				n == 0
 				and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
 				and not self.opening_accumulated_depreciation
+				and get_updated_rate_of_depreciation_for_wdv_and_dd(
+					asset_doc, value_after_depreciation, row, False
+				)
+				== row.rate_of_depreciation
 			):
 				from_date = add_days(
 					asset_doc.available_for_use_date, -1
@@ -605,7 +609,9 @@
 
 
 @erpnext.allow_regional
-def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row):
+def get_updated_rate_of_depreciation_for_wdv_and_dd(
+	asset, depreciable_value, fb_row, show_msg=True
+):
 	return fb_row.rate_of_depreciation
 
 
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py
index 0d8efcb..ff52643 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.py
@@ -84,7 +84,7 @@
 					frappe.throw(_("Source and Target Location cannot be same"))
 
 			if self.purpose == "Receipt":
-				if not (d.source_location) and not (d.target_location or d.to_employee):
+				if not (d.source_location) and not d.target_location and not d.to_employee:
 					frappe.throw(
 						_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
 					)
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index c0fb3c2..bb627d4 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -93,6 +93,10 @@
 
 			self.increase_asset_value()
 
+			if self.capitalize_repair_cost:
+				self.asset_doc.total_asset_cost += self.repair_cost
+				self.asset_doc.additional_asset_cost += self.repair_cost
+
 			if self.get("stock_consumption"):
 				self.check_for_stock_items_and_warehouse()
 				self.decrease_stock_quantity()
@@ -128,6 +132,10 @@
 
 			self.decrease_asset_value()
 
+			if self.capitalize_repair_cost:
+				self.asset_doc.total_asset_cost -= self.repair_cost
+				self.asset_doc.additional_asset_cost -= self.repair_cost
+
 			if self.get("stock_consumption"):
 				self.increase_stock_quantity()
 			if self.get("capitalize_repair_cost"):
@@ -277,7 +285,9 @@
 					"account": fixed_asset_account,
 					"debit": self.repair_cost,
 					"debit_in_account_currency": self.repair_cost,
+					"against_type": "Account",
 					"against": pi_expense_account,
+					"against_link": pi_expense_account,
 					"voucher_type": self.doctype,
 					"voucher_no": self.name,
 					"cost_center": self.cost_center,
@@ -296,7 +306,9 @@
 					"account": pi_expense_account,
 					"credit": self.repair_cost,
 					"credit_in_account_currency": self.repair_cost,
+					"against_type": "Account",
 					"against": fixed_asset_account,
+					"against_link": fixed_asset_account,
 					"voucher_type": self.doctype,
 					"voucher_no": self.name,
 					"cost_center": self.cost_center,
@@ -330,7 +342,9 @@
 							"account": item.expense_account or default_expense_account,
 							"credit": item.amount,
 							"credit_in_account_currency": item.amount,
+							"against_type": "Account",
 							"against": fixed_asset_account,
+							"against_link": fixed_asset_account,
 							"voucher_type": self.doctype,
 							"voucher_no": self.name,
 							"cost_center": self.cost_center,
@@ -347,7 +361,9 @@
 							"account": fixed_asset_account,
 							"debit": item.amount,
 							"debit_in_account_currency": item.amount,
+							"against_type": "Account",
 							"against": item.expense_account or default_expense_account,
+							"against_link": item.expense_account or default_expense_account,
 							"voucher_type": self.doctype,
 							"voucher_no": self.name,
 							"cost_center": self.cost_center,
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 88faeee..3b671bb 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -214,7 +214,7 @@
 		}
 	},
 
-	fg_item_qty: async function(frm, cdt, cdn) {
+	qty: async function (frm, cdt, cdn) {
 		if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
 			var row = locals[cdt][cdn];
 
@@ -222,7 +222,7 @@
 				var result = await frm.events.get_subcontracting_boms_for_finished_goods(row.fg_item)
 
 				if (result.message && row.item_code == result.message.service_item && row.uom == result.message.service_item_uom) {
-					frappe.model.set_value(cdt, cdn, "qty", flt(row.fg_item_qty) * flt(result.message.conversion_factor));
+					frappe.model.set_value(cdt, cdn, "fg_item_qty", flt(row.qty) / flt(result.message.conversion_factor));
 				}
 			}
 		}
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 595722d..2efb46e 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -608,6 +608,20 @@
 
 		return result
 
+	def update_ordered_qty_in_so_for_removed_items(self, removed_items):
+		"""
+		Updates ordered_qty in linked SO when item rows are removed using Update Items
+		"""
+		if not self.is_against_so():
+			return
+		for item in removed_items:
+			prev_ordered_qty = frappe.get_cached_value(
+				"Sales Order Item", item.get("sales_order_item"), "ordered_qty"
+			)
+			frappe.db.set_value(
+				"Sales Order Item", item.get("sales_order_item"), "ordered_qty", prev_ordered_qty - item.qty
+			)
+
 	def auto_create_subcontracting_order(self):
 		if self.is_subcontracted and not self.is_old_subcontracting_flow:
 			if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
@@ -699,8 +713,6 @@
 		set_missing_values,
 	)
 
-	doc.set_onload("ignore_price_list", True)
-
 	return doc
 
 
@@ -780,7 +792,6 @@
 		postprocess,
 		ignore_permissions=ignore_permissions,
 	)
-	doc.set_onload("ignore_price_list", True)
 
 	return doc
 
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 98c1b38..5a24cc2 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -123,8 +123,7 @@
    "oldfieldname": "item_code",
    "oldfieldtype": "Link",
    "options": "Item",
-   "reqd": 1,
-   "search_index": 1
+   "reqd": 1
   },
   {
    "fieldname": "supplier_part_no",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index eea8cd5..5b8be44 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -119,6 +119,15 @@
 			supplier.quote_status = "Pending"
 		self.send_to_supplier()
 
+	def before_print(self, settings=None):
+		"""Use the first suppliers data to render the print preview."""
+		if self.vendor or not self.suppliers:
+			# If a specific supplier is already set, via Tools > Download PDF,
+			# we don't want to override it.
+			return
+
+		self.update_supplier_part_no(self.suppliers[0].supplier)
+
 	def on_cancel(self):
 		self.db_set("status", "Cancelled")
 
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index bb0c269..e2b737b 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -257,7 +257,6 @@
 		set_missing_values,
 	)
 
-	doclist.set_onload("ignore_price_list", True)
 	return doclist
 
 
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
index b6e4630..b88efe1 100644
--- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
@@ -114,7 +114,7 @@
 		if filters.get("group_by_po"):
 			po_name = row["purchase_order"]
 
-			if not po_name in purchase_order_map:
+			if po_name not in purchase_order_map:
 				# create an entry
 				row_copy = copy.deepcopy(row)
 				purchase_order_map[po_name] = row_copy
diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
index 0718735..d431010 100644
--- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
@@ -110,7 +110,7 @@
 
 	for row in data:
 		# item wise map for charts
-		if not row["item_code"] in item_qty_map:
+		if row["item_code"] not in item_qty_map:
 			item_qty_map[row["item_code"]] = {
 				"qty": flt(row["stock_qty"], precision),
 				"stock_qty": flt(row["stock_qty"], precision),
@@ -127,7 +127,7 @@
 
 		if filters.get("group_by_mr"):
 			# consolidated material request map for group by filter
-			if not row["material_request"] in material_request_map:
+			if row["material_request"] not in material_request_map:
 				# create an entry with mr as key
 				row_copy = copy.deepcopy(row)
 				material_request_map[row["material_request"]] = row_copy
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
index 01ff28d..73b7d45 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
@@ -126,7 +126,7 @@
 		# map for chart preparation of the form {'supplier1': {'qty': 'price'}}
 		supplier = data.get("supplier_name")
 		if filters.get("item_code"):
-			if not supplier in supplier_qty_price_map:
+			if supplier not in supplier_qty_price_map:
 				supplier_qty_price_map[supplier] = {}
 			supplier_qty_price_map[supplier][row["qty"]] = row["price"]
 
@@ -169,7 +169,7 @@
 	for supplier in suppliers:
 		entry = supplier_qty_price_map[supplier]
 		for qty in qty_list:
-			if not qty in data_points_map:
+			if qty not in data_points_map:
 				data_points_map[qty] = []
 			if qty in entry:
 				data_points_map[qty].append(entry[qty])
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index f551133..febad18 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -166,6 +166,7 @@
 		self.disable_pricing_rule_on_internal_transfer()
 		self.disable_tax_included_prices_for_internal_transfer()
 		self.set_incoming_rate()
+		self.init_internal_values()
 
 		if self.meta.get_field("currency"):
 			self.calculate_taxes_and_totals()
@@ -225,6 +226,16 @@
 
 		self.set_total_in_words()
 
+	def init_internal_values(self):
+		# init all the internal values as 0 on sa
+		if self.docstatus.is_draft():
+			# TODO: Add all such pending values here
+			fields = ["billed_amt", "delivered_qty"]
+			for item in self.get("items"):
+				for field in fields:
+					if hasattr(item, field):
+						item.set(field, 0)
+
 	def before_cancel(self):
 		validate_einvoice_fields(self)
 
@@ -292,6 +303,7 @@
 	def on_trash(self):
 		self._remove_references_in_repost_doctypes()
 		self._remove_references_in_unreconcile()
+		self.remove_serial_and_batch_bundle()
 
 		# delete sl and gl entries on deletion of transaction
 		if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
@@ -307,6 +319,15 @@
 				(self.doctype, self.name),
 			)
 
+	def remove_serial_and_batch_bundle(self):
+		bundles = frappe.get_all(
+			"Serial and Batch Bundle",
+			filters={"voucher_type": self.doctype, "voucher_no": self.name, "docstatus": ("!=", 1)},
+		)
+
+		for bundle in bundles:
+			frappe.delete_doc("Serial and Batch Bundle", bundle.name)
+
 	def validate_deferred_income_expense_account(self):
 		field_map = {
 			"Sales Invoice": "deferred_revenue_account",
@@ -853,6 +874,7 @@
 				"project": self.get("project"),
 				"post_net_value": args.get("post_net_value"),
 				"voucher_detail_no": args.get("voucher_detail_no"),
+				"voucher_subtype": self.get_voucher_subtype(),
 			}
 		)
 
@@ -908,6 +930,25 @@
 
 		return gl_dict
 
+	def get_voucher_subtype(self):
+		voucher_subtypes = {
+			"Journal Entry": "voucher_type",
+			"Payment Entry": "payment_type",
+			"Stock Entry": "stock_entry_type",
+			"Asset Capitalization": "entry_type",
+		}
+		if self.doctype in voucher_subtypes:
+			return self.get(voucher_subtypes[self.doctype])
+		elif self.doctype == "Purchase Receipt" and self.is_return:
+			return "Purchase Return"
+		elif self.doctype == "Delivery Note" and self.is_return:
+			return "Sales Return"
+		elif (self.doctype == "Sales Invoice" and self.is_return) or self.doctype == "Purchase Invoice":
+			return "Credit Note"
+		elif (self.doctype == "Purchase Invoice" and self.is_return) or self.doctype == "Sales Invoice":
+			return "Debit Note"
+		return self.doctype
+
 	def get_value_in_transaction_currency(self, account_currency, args, field):
 		if account_currency == self.get("currency"):
 			return args.get(field + "_in_account_currency")
@@ -1099,6 +1140,7 @@
 		)
 
 		credit_or_debit = "credit" if self.doctype == "Purchase Invoice" else "debit"
+		against_type = "Supplier" if self.doctype == "Purchase Invoice" else "Customer"
 		against = self.supplier if self.doctype == "Purchase Invoice" else self.customer
 
 		if precision_loss:
@@ -1106,7 +1148,9 @@
 				self.get_gl_dict(
 					{
 						"account": round_off_account,
+						"against_type": against_type,
 						"against": against,
+						"against_link": against,
 						credit_or_debit: precision_loss,
 						"cost_center": round_off_cost_center
 						if self.use_company_roundoff_cost_center
@@ -1455,11 +1499,13 @@
 		if self.doctype == "Purchase Invoice":
 			dr_or_cr = "credit"
 			rev_dr_cr = "debit"
+			against_type = "Supplier"
 			supplier_or_customer = self.supplier
 
 		else:
 			dr_or_cr = "debit"
 			rev_dr_cr = "credit"
+			against_type = "Customer"
 			supplier_or_customer = self.customer
 
 		if enable_discount_accounting:
@@ -1484,7 +1530,9 @@
 						self.get_gl_dict(
 							{
 								"account": item.discount_account,
+								"against_type": against_type,
 								"against": supplier_or_customer,
+								"against_link": supplier_or_customer,
 								dr_or_cr: flt(
 									discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
 								),
@@ -1502,7 +1550,9 @@
 						self.get_gl_dict(
 							{
 								"account": income_or_expense_account,
+								"against_type": against_type,
 								"against": supplier_or_customer,
+								"against_link": supplier_or_customer,
 								rev_dr_cr: flt(
 									discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
 								),
@@ -1525,7 +1575,9 @@
 				self.get_gl_dict(
 					{
 						"account": self.additional_discount_account,
+						"against_type": against_type,
 						"against": supplier_or_customer,
+						"against_link": supplier_or_customer,
 						dr_or_cr: self.base_discount_amount,
 						"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
 					},
@@ -2958,6 +3010,9 @@
 		d.cancel()
 		d.delete()
 
+	if parent.doctype == "Purchase Order":
+		parent.update_ordered_qty_in_so_for_removed_items(deleted_children)
+
 	# need to update ordered qty in Material Request first
 	# bin uses Material Request Items to recalculate & update
 	parent.update_prevdoc_status()
@@ -3248,7 +3303,10 @@
 
 	if parent_doctype == "Purchase Order":
 		update_last_purchase_rate(parent, is_submit=1)
-		parent.update_prevdoc_status()
+
+		if any_qty_changed or items_added_or_removed or any_conversion_factor_changed:
+			parent.update_prevdoc_status()
+
 		parent.update_requested_qty()
 		parent.update_ordered_qty()
 		parent.update_ordered_and_reserved_qty()
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 54c97b4..572fa51 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -118,6 +118,7 @@
 							"company": self.company,
 							"voucher_type": self.doctype,
 							"voucher_no": self.name,
+							"voucher_detail_no": row.name,
 						},
 						raise_error_if_no_rate=False,
 					)
@@ -373,13 +374,18 @@
 							"voucher_type": self.doctype,
 							"voucher_no": self.name,
 							"allow_zero_valuation": d.get("allow_zero_valuation"),
+							"voucher_detail_no": d.name,
 						},
 						raise_error_if_no_rate=False,
 					)
 
 					rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
 				else:
-					field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
+					field = (
+						"incoming_rate"
+						if self.get("is_internal_supplier") and not self.doctype == "Purchase Order"
+						else "rate"
+					)
 					rate = flt(
 						frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
 						* (d.conversion_factor or 1),
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index c8785a5..ea7fb23 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -56,10 +56,24 @@
 
 	copy_attributes_to_variant(template, variant)
 
-	variant.manufacturer = manufacturer
-	variant.manufacturer_part_no = manufacturer_part_no
-
 	variant.item_code = append_number_if_name_exists("Item", template.name)
+	variant.flags.ignore_mandatory = True
+	variant.save()
+
+	if not frappe.db.exists(
+		"Item Manufacturer", {"item_code": variant.name, "manufacturer": manufacturer}
+	):
+		manufacturer_doc = frappe.new_doc("Item Manufacturer")
+		manufacturer_doc.update(
+			{
+				"item_code": variant.name,
+				"manufacturer": manufacturer,
+				"manufacturer_part_no": manufacturer_part_no,
+			}
+		)
+
+		manufacturer_doc.flags.ignore_mandatory = True
+		manufacturer_doc.save(ignore_permissions=True)
 
 	return variant
 
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 199732b..2650753 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -222,7 +222,7 @@
 	searchfields = meta.get_search_fields()
 
 	columns = ""
-	extra_searchfields = [field for field in searchfields if not field in ["name", "description"]]
+	extra_searchfields = [field for field in searchfields if field not in ["name", "description"]]
 
 	if extra_searchfields:
 		columns += ", " + ", ".join(extra_searchfields)
@@ -233,8 +233,13 @@
 
 	searchfields = searchfields + [
 		field
-		for field in [searchfield or "name", "item_code", "item_group", "item_name"]
-		if not field in searchfields
+		for field in [
+			searchfield or "name",
+			"item_code",
+			"item_group",
+			"item_name",
+		]
+		if field not in searchfields
 	]
 	searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
 
@@ -872,7 +877,7 @@
 	meta = frappe.get_meta(doctype)
 	fields.extend(meta.get_search_fields())
 
-	if meta.title_field and not meta.title_field.strip() in fields:
+	if meta.title_field and meta.title_field.strip() not in fields:
 		fields.insert(1, meta.title_field.strip())
 
 	return unique(fields)
@@ -891,3 +896,31 @@
 			as_list=1,
 		)
 	return terms
+
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_filtered_child_rows(doctype, txt, searchfield, start, page_len, filters) -> list:
+	table = frappe.qb.DocType(doctype)
+	query = (
+		frappe.qb.from_(table)
+		.select(
+			table.name,
+			Concat("#", table.idx, ", ", table.item_code),
+		)
+		.orderby(table.idx)
+		.offset(start)
+		.limit(page_len)
+	)
+
+	if filters:
+		for field, value in filters.items():
+			query = query.where(table[field] == value)
+
+	if txt:
+		txt += "%"
+		query = query.where(
+			((table.idx.like(txt.replace("#", ""))) | (table.item_code.like(txt))) | (table.name.like(txt))
+		)
+
+	return query.run(as_dict=False)
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 8e3a15a..81080f0 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -8,6 +8,8 @@
 from frappe.utils import flt, format_datetime, get_datetime
 
 import erpnext
+from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
+from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
 from erpnext.stock.utils import get_incoming_rate
 
 
@@ -69,8 +71,6 @@
 
 
 def validate_returned_items(doc):
-	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
-
 	valid_items = frappe._dict()
 
 	select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor"
@@ -123,26 +123,6 @@
 						)
 					)
 
-				elif ref.batch_no and d.batch_no not in ref.batch_no:
-					frappe.throw(
-						_("Row # {0}: Batch No must be same as {1} {2}").format(
-							d.idx, doc.doctype, doc.return_against
-						)
-					)
-
-				elif ref.serial_no:
-					if d.qty and not d.serial_no:
-						frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
-					else:
-						serial_nos = get_serial_nos(d.serial_no)
-						for s in serial_nos:
-							if s not in ref.serial_no:
-								frappe.throw(
-									_("Row # {0}: Serial No {1} does not match with {2} {3}").format(
-										d.idx, s, doc.doctype, doc.return_against
-									)
-								)
-
 				if (
 					warehouse_mandatory
 					and not d.get("warehouse")
@@ -397,71 +377,92 @@
 		else:
 			doc.run_method("calculate_taxes_and_totals")
 
-	def update_item(source_doc, target_doc, source_parent):
+	def update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field):
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 		from erpnext.stock.serial_batch_bundle import SerialBatchCreation
 
-		target_doc.qty = -1 * source_doc.qty
-		item_details = frappe.get_cached_value(
-			"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
-		)
-
 		returned_serial_nos = []
-		if source_doc.get("serial_and_batch_bundle"):
-			if item_details.has_serial_no:
-				returned_serial_nos = get_returned_serial_nos(source_doc, source_parent)
+		returned_batches = frappe._dict()
+		serial_and_batch_field = (
+			"serial_and_batch_bundle" if qty_field == "stock_qty" else "rejected_serial_and_batch_bundle"
+		)
+		old_serial_no_field = "serial_no" if qty_field == "stock_qty" else "rejected_serial_no"
+		old_batch_no_field = "batch_no"
 
-			type_of_transaction = "Inward"
-			if (
-				frappe.db.get_value(
-					"Serial and Batch Bundle", source_doc.serial_and_batch_bundle, "type_of_transaction"
-				)
-				== "Inward"
-			):
-				type_of_transaction = "Outward"
-
-			cls_obj = SerialBatchCreation(
-				{
-					"type_of_transaction": type_of_transaction,
-					"serial_and_batch_bundle": source_doc.serial_and_batch_bundle,
-					"returned_against": source_doc.name,
-					"item_code": source_doc.item_code,
-					"returned_serial_nos": returned_serial_nos,
-				}
-			)
-
-			cls_obj.duplicate_package()
-			if cls_obj.serial_and_batch_bundle:
-				target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
-
-		if source_doc.get("rejected_serial_and_batch_bundle"):
+		if (
+			source_doc.get(serial_and_batch_field)
+			or source_doc.get(old_serial_no_field)
+			or source_doc.get(old_batch_no_field)
+		):
 			if item_details.has_serial_no:
 				returned_serial_nos = get_returned_serial_nos(
-					source_doc, source_parent, serial_no_field="rejected_serial_and_batch_bundle"
+					source_doc, source_parent, serial_no_field=serial_and_batch_field
+				)
+			else:
+				returned_batches = get_returned_batches(
+					source_doc, source_parent, batch_no_field=serial_and_batch_field
 				)
 
 			type_of_transaction = "Inward"
-			if (
+			if source_doc.get(serial_and_batch_field) and (
 				frappe.db.get_value(
-					"Serial and Batch Bundle", source_doc.rejected_serial_and_batch_bundle, "type_of_transaction"
+					"Serial and Batch Bundle", source_doc.get(serial_and_batch_field), "type_of_transaction"
 				)
 				== "Inward"
 			):
 				type_of_transaction = "Outward"
+			elif source_parent.doctype in [
+				"Purchase Invoice",
+				"Purchase Receipt",
+				"Subcontracting Receipt",
+			]:
+				type_of_transaction = "Outward"
 
 			cls_obj = SerialBatchCreation(
 				{
 					"type_of_transaction": type_of_transaction,
-					"serial_and_batch_bundle": source_doc.rejected_serial_and_batch_bundle,
+					"serial_and_batch_bundle": source_doc.get(serial_and_batch_field),
 					"returned_against": source_doc.name,
 					"item_code": source_doc.item_code,
 					"returned_serial_nos": returned_serial_nos,
+					"voucher_type": source_parent.doctype,
+					"do_not_submit": True,
+					"warehouse": source_doc.warehouse,
+					"has_serial_no": item_details.has_serial_no,
+					"has_batch_no": item_details.has_batch_no,
 				}
 			)
 
-			cls_obj.duplicate_package()
-			if cls_obj.serial_and_batch_bundle:
-				target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
+			serial_nos = []
+			batches = frappe._dict()
+			if source_doc.get(old_batch_no_field):
+				batches = frappe._dict({source_doc.batch_no: source_doc.get(qty_field)})
+			elif source_doc.get(old_serial_no_field):
+				serial_nos = get_serial_nos(source_doc.get(old_serial_no_field))
+			elif source_doc.get(serial_and_batch_field):
+				if item_details.has_serial_no:
+					serial_nos = get_serial_nos_from_bundle(source_doc.get(serial_and_batch_field))
+				else:
+					batches = get_batches_from_bundle(source_doc.get(serial_and_batch_field))
 
+			if serial_nos:
+				cls_obj.serial_nos = sorted(list(set(serial_nos) - set(returned_serial_nos)))
+			elif batches:
+				for batch in batches:
+					if batch in returned_batches:
+						batches[batch] -= flt(returned_batches.get(batch))
+
+				cls_obj.batches = batches
+
+			if source_doc.get(serial_and_batch_field):
+				cls_obj.duplicate_package()
+				if cls_obj.serial_and_batch_bundle:
+					target_doc.set(serial_and_batch_field, cls_obj.serial_and_batch_bundle)
+			else:
+				target_doc.set(serial_and_batch_field, cls_obj.make_serial_and_batch_bundle().name)
+
+	def update_item(source_doc, target_doc, source_parent):
+		target_doc.qty = -1 * source_doc.qty
 		if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
 			returned_qty_map = get_returned_qty_map_for_row(
 				source_parent.name, source_parent.supplier, source_doc.name, doctype
@@ -561,6 +562,17 @@
 			if default_warehouse_for_sales_return:
 				target_doc.warehouse = default_warehouse_for_sales_return
 
+		item_details = frappe.get_cached_value(
+			"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
+		)
+
+		if not item_details.has_batch_no and not item_details.has_serial_no:
+			return
+
+		for qty_field in ["stock_qty", "rejected_qty"]:
+			if target_doc.get(qty_field):
+				update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
+
 	def update_terms(source_doc, target_doc, source_parent):
 		target_doc.payment_amount = -source_doc.payment_amount
 
@@ -586,8 +598,6 @@
 		set_missing_values,
 	)
 
-	doclist.set_onload("ignore_price_list", True)
-
 	return doclist
 
 
@@ -718,6 +728,9 @@
 		[parent_doc.doctype, "docstatus", "=", 1],
 	]
 
+	if serial_no_field == "rejected_serial_and_batch_bundle":
+		filters.append([child_doc.doctype, "rejected_qty", ">", 0])
+
 	# Required for POS Invoice
 	if ignore_voucher_detail_no:
 		filters.append([child_doc.doctype, "name", "!=", ignore_voucher_detail_no])
@@ -725,9 +738,57 @@
 	ids = []
 	for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
 		ids.append(row.get("serial_and_batch_bundle"))
-		if row.get(old_field):
+		if row.get(old_field) and not row.get(serial_no_field):
 			serial_nos.extend(get_serial_nos_from_serial_no(row.get(old_field)))
 
-	serial_nos.extend(get_serial_nos(ids))
+	if ids:
+		serial_nos.extend(get_serial_nos(ids))
 
 	return serial_nos
+
+
+def get_returned_batches(
+	child_doc, parent_doc, batch_no_field=None, ignore_voucher_detail_no=None
+):
+	from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
+
+	batches = frappe._dict()
+
+	old_field = "batch_no"
+	if not batch_no_field:
+		batch_no_field = "serial_and_batch_bundle"
+
+	return_ref_field = frappe.scrub(child_doc.doctype)
+	if child_doc.doctype == "Delivery Note Item":
+		return_ref_field = "dn_detail"
+
+	fields = [
+		f"`{'tab' + child_doc.doctype}`.`{batch_no_field}`",
+		f"`{'tab' + child_doc.doctype}`.`batch_no`",
+		f"`{'tab' + child_doc.doctype}`.`stock_qty`",
+	]
+
+	filters = [
+		[parent_doc.doctype, "return_against", "=", parent_doc.name],
+		[parent_doc.doctype, "is_return", "=", 1],
+		[child_doc.doctype, return_ref_field, "=", child_doc.name],
+		[parent_doc.doctype, "docstatus", "=", 1],
+	]
+
+	if batch_no_field == "rejected_serial_and_batch_bundle":
+		filters.append([child_doc.doctype, "rejected_qty", ">", 0])
+
+	# Required for POS Invoice
+	if ignore_voucher_detail_no:
+		filters.append([child_doc.doctype, "name", "!=", ignore_voucher_detail_no])
+
+	ids = []
+	for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
+		ids.append(row.get("serial_and_batch_bundle"))
+		if row.get(old_field) and not row.get(batch_no_field):
+			batches.setdefault(row.get(old_field), row.get("stock_qty"))
+
+	if ids:
+		batches.update(get_batches_from_bundle(ids))
+
+	return batches
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 5575a24..919e459 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -12,7 +12,7 @@
 from erpnext.controllers.stock_controller import StockController
 from erpnext.stock.doctype.item.item import set_item_default
 from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor
-from erpnext.stock.utils import get_incoming_rate
+from erpnext.stock.utils import get_incoming_rate, get_valuation_method
 
 
 class SellingController(StockController):
@@ -308,6 +308,8 @@
 									"warehouse": p.warehouse or d.warehouse,
 									"item_code": p.item_code,
 									"qty": flt(p.qty),
+									"serial_no": p.serial_no if self.docstatus == 2 else None,
+									"batch_no": p.batch_no if self.docstatus == 2 else None,
 									"uom": p.uom,
 									"serial_and_batch_bundle": p.serial_and_batch_bundle
 									or get_serial_and_batch_bundle(p, self),
@@ -330,6 +332,8 @@
 							"warehouse": d.warehouse,
 							"item_code": d.item_code,
 							"qty": d.stock_qty,
+							"serial_no": d.serial_no if self.docstatus == 2 else None,
+							"batch_no": d.batch_no if self.docstatus == 2 else None,
 							"uom": d.uom,
 							"stock_uom": d.stock_uom,
 							"conversion_factor": d.conversion_factor,
@@ -428,11 +432,15 @@
 
 		items = self.get("items") + (self.get("packed_items") or [])
 		for d in items:
-			if not self.get("return_against"):
+			if not self.get("return_against") or (
+				get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
+			):
 				# Get incoming rate based on original item cost based on valuation method
 				qty = flt(d.get("stock_qty") or d.get("actual_qty"))
 
-				if not (self.get("is_return") and d.incoming_rate):
+				if not d.incoming_rate or (
+					get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
+				):
 					d.incoming_rate = get_incoming_rate(
 						{
 							"item_code": d.item_code,
@@ -444,6 +452,7 @@
 							"company": self.company,
 							"voucher_type": self.doctype,
 							"voucher_no": self.name,
+							"voucher_detail_no": d.name,
 							"allow_zero_valuation": d.get("allow_zero_valuation"),
 						},
 						raise_error_if_no_rate=False,
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index fc45c7a..671d2fb 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -162,7 +162,9 @@
 							self.get_gl_dict(
 								{
 									"account": warehouse_account[sle.warehouse]["account"],
+									"against_type": "Account",
 									"against": expense_account,
+									"against_link": expense_account,
 									"cost_center": item_row.cost_center,
 									"project": item_row.project or self.get("project"),
 									"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@@ -178,7 +180,9 @@
 							self.get_gl_dict(
 								{
 									"account": expense_account,
+									"against_type": "Account",
 									"against": warehouse_account[sle.warehouse]["account"],
+									"against_link": warehouse_account[sle.warehouse]["account"],
 									"cost_center": item_row.cost_center,
 									"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
 									"debit": -1 * flt(sle.stock_value_difference, precision),
@@ -210,7 +214,9 @@
 					self.get_gl_dict(
 						{
 							"account": expense_account,
+							"against_type": "Account",
 							"against": warehouse_asset_account,
+							"against_link": warehouse_asset_account,
 							"cost_center": item_row.cost_center,
 							"project": item_row.project or self.get("project"),
 							"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
@@ -226,7 +232,9 @@
 					self.get_gl_dict(
 						{
 							"account": warehouse_asset_account,
+							"against_type": "Account",
 							"against": expense_account,
+							"against_link": expense_account,
 							"cost_center": item_row.cost_center,
 							"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
 							"credit": sle_rounding_diff,
@@ -455,6 +463,12 @@
 		sl_dict.update(args)
 		self.update_inventory_dimensions(d, sl_dict)
 
+		if self.docstatus == 2:
+			# To handle denormalized serial no records, will br deprecated in v16
+			for field in ["serial_no", "batch_no"]:
+				if d.get(field):
+					sl_dict[field] = d.get(field)
+
 		return sl_dict
 
 	def update_inventory_dimensions(self, row, sl_dict) -> None:
@@ -642,7 +656,7 @@
 		)
 		qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus")
 
-		if not qa_docstatus == 1:
+		if qa_docstatus != 1:
 			link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
 			msg = (
 				f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
@@ -826,6 +840,7 @@
 		credit,
 		remarks,
 		against_account,
+		against_type="Account",
 		debit_in_account_currency=None,
 		credit_in_account_currency=None,
 		account_currency=None,
@@ -840,7 +855,9 @@
 			"cost_center": cost_center,
 			"debit": debit,
 			"credit": credit,
+			"against_type": against_type,
 			"against": against_account,
+			"against_link": against_account,
 			"remarks": remarks,
 		}
 
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index b52fbad..9555902 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -880,6 +880,7 @@
 							"posting_date": self.posting_date,
 							"posting_time": self.posting_time,
 							"qty": -1 * item.consumed_qty,
+							"voucher_detail_no": item.name,
 							"serial_and_batch_bundle": item.serial_and_batch_bundle,
 						}
 					)
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 7be6fdc..a735510 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -55,7 +55,7 @@
 			"Appointment", filters={"scheduled_time": self.scheduled_time}
 		)
 		number_of_agents = frappe.db.get_single_value("Appointment Booking Settings", "number_of_agents")
-		if not number_of_agents == 0:
+		if number_of_agents != 0:
 			if number_of_appointments_in_same_slot >= number_of_agents:
 				frappe.throw(_("Time slot is not available"))
 		# Link lead
@@ -110,7 +110,7 @@
 		cal_event.save(ignore_permissions=True)
 
 	def set_verified(self, email):
-		if not email == self.customer_email:
+		if email != self.customer_email:
 			frappe.throw(_("Email verification failed."))
 		# Create new lead
 		self.create_lead_and_link()
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index dafbd9f..92f446d 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -516,7 +516,7 @@
  "idx": 5,
  "image_field": "image",
  "links": [],
- "modified": "2023-08-28 22:28:00.104413",
+ "modified": "2023-12-01 18:46:49.468526",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Lead",
@@ -577,6 +577,7 @@
  ],
  "search_fields": "lead_name,lead_owner,status",
  "sender_field": "email_id",
+ "sender_name_field": "lead_name",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index f0fc1aa..f3c7e57 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -16,6 +16,7 @@
 from erpnext.accounts.party import set_taxes
 from erpnext.controllers.selling_controller import SellingController
 from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
+from erpnext.selling.doctype.customer.customer import parse_full_name
 
 
 class Lead(SellingController, CRMNote):
@@ -105,7 +106,7 @@
 			if self.source == "Existing Customer" and self.customer:
 				contact = frappe.db.get_value(
 					"Dynamic Link",
-					{"link_doctype": "Customer", "link_name": self.customer},
+					{"link_doctype": "Customer", "parenttype": "Contact", "link_name": self.customer},
 					"parent",
 				)
 				if contact:
@@ -113,6 +114,10 @@
 					return
 			self.contact_doc = self.create_contact()
 
+		# leads created by email inbox only have the full name set
+		if self.lead_name and not any([self.first_name, self.middle_name, self.last_name]):
+			self.first_name, self.middle_name, self.last_name = parse_full_name(self.lead_name)
+
 	def after_insert(self):
 		self.link_to_contact()
 
diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py
index 8984f1b..1924ffb 100644
--- a/erpnext/erpnext_integrations/utils.py
+++ b/erpnext/erpnext_integrations/utils.py
@@ -16,7 +16,7 @@
 				hmac.new(settings.get(secret_key).encode("utf8"), frappe.request.data, hashlib.sha256).digest()
 			)
 
-			if frappe.request.data and not sig == bytes(frappe.get_request_header(hmac_key).encode()):
+			if frappe.request.data and sig != bytes(frappe.get_request_header(hmac_key).encode()):
 				frappe.throw(_("Unverified Webhook Data"))
 			frappe.set_user(settings.modified_by)
 
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 17ad155..f6b6802 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -637,6 +637,7 @@
 
 extend_bootinfo = [
 	"erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes",
+	"erpnext.startup.boot.bootinfo",
 ]
 
 
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index b5ab63e..6a72c4f 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -89,7 +89,7 @@
 
 	def update_item(source, target, source_parent):
 		target_qty = source.get("qty") - source.get("ordered_qty")
-		target.qty = target_qty if not flt(target_qty) < 0 else 0
+		target.qty = target_qty if flt(target_qty) >= 0 else 0
 		item = get_item_defaults(target.item_code, source_parent.company)
 		if item:
 			target.item_name = item.get("item_name")
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index e8d3542..5083873 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -218,6 +218,7 @@
    "options": "\nWork Order\nJob Card"
   },
   {
+   "default": "1",
    "fieldname": "conversion_rate",
    "fieldtype": "Float",
    "label": "Conversion Rate",
@@ -636,7 +637,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-08-07 11:38:08.152294",
+ "modified": "2023-12-26 19:34:08.159312",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 71015a4..d86b6d4 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -744,6 +744,9 @@
 		base_total_rm_cost = 0
 
 		for d in self.get("items"):
+			if not d.is_stock_item and self.rm_cost_as_per == "Valuation Rate":
+				continue
+
 			old_rate = d.rate
 			if self.rm_cost_as_per != "Manual":
 				d.rate = self.get_rm_rate(
@@ -1017,6 +1020,8 @@
 		item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
 		price_list_data = get_price_list_rate(bom_args, item_doc)
 		rate = price_list_data.price_list_rate
+	elif bom_doc.rm_cost_as_per == "Manual":
+		return
 
 	return flt(rate)
 
@@ -1381,7 +1386,7 @@
 
 			# check for deletions
 			for d in old_value:
-				if not d.get(identifier) in new_row_by_identifier:
+				if d.get(identifier) not in new_row_by_identifier:
 					out.removed.append([df.fieldname, d.as_dict()])
 
 	return out
@@ -1397,13 +1402,18 @@
 
 	fields = ["name", "item_name", "item_group", "description"]
 	fields.extend(
-		[field for field in searchfields if not field in ["name", "item_group", "description"]]
+		[field for field in searchfields if field not in ["name", "item_group", "description"]]
 	)
 
 	searchfields = searchfields + [
 		field
-		for field in [searchfield or "name", "item_code", "item_group", "item_name"]
-		if not field in searchfields
+		for field in [
+			searchfield or "name",
+			"item_code",
+			"item_group",
+			"item_name",
+		]
+		if field not in searchfields
 	]
 
 	query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())}
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 051b475..2debf91 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -698,6 +698,35 @@
 		bom.update_cost()
 		self.assertFalse(bom.flags.cost_updated)
 
+	def test_bom_with_service_item_cost(self):
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+		rm_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 1000.0}).name
+
+		service_item = make_item(properties={"is_stock_item": 0}).name
+
+		fg_item = make_item(properties={"is_stock_item": 1}).name
+
+		from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+		bom = make_bom(item=fg_item, raw_materials=[rm_item, service_item], do_not_save=True)
+		bom.rm_cost_as_per = "Valuation Rate"
+
+		for row in bom.items:
+			if row.item_code == service_item:
+				row.rate = 566.00
+			else:
+				row.rate = 800.00
+
+		bom.save()
+
+		for row in bom.items:
+			if row.item_code == service_item:
+				self.assertEqual(row.is_stock_item, 0)
+				self.assertEqual(row.rate, 566.00)
+			else:
+				self.assertEqual(row.is_stock_item, 1)
+
 	def test_do_not_include_manufacturing_and_fixed_items(self):
 		from erpnext.manufacturing.doctype.bom.bom import item_query
 
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index cb58af1..dfd6612 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -14,6 +14,7 @@
   "bom_no",
   "source_warehouse",
   "allow_alternative_item",
+  "is_stock_item",
   "section_break_5",
   "description",
   "col_break1",
@@ -185,7 +186,7 @@
    "in_list_view": 1,
    "label": "Rate",
    "options": "currency",
-   "read_only": 1,
+   "read_only_depends_on": "eval:doc.is_stock_item == 1",
    "reqd": 1
   },
   {
@@ -284,13 +285,21 @@
    "fieldname": "do_not_explode",
    "fieldtype": "Check",
    "label": "Do Not Explode"
+  },
+  {
+   "default": "0",
+   "fetch_from": "item_code.is_stock_item",
+   "fieldname": "is_stock_item",
+   "fieldtype": "Check",
+   "label": "Is Stock Item",
+   "read_only": 1
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-14 18:35:51.378513",
+ "modified": "2023-12-20 16:21:55.477883",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Item",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 4b1015d..23650b6 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -261,8 +261,7 @@
 			# override capacity for employee
 			production_capacity = 1
 
-		overlap_count = self.get_overlap_count(time_logs)
-		if time_logs and production_capacity > overlap_count:
+		if not self.has_overlap(production_capacity, time_logs):
 			return {}
 
 		if self.workstation_type and time_logs:
@@ -272,36 +271,42 @@
 
 		return time_logs[-1]
 
-	@staticmethod
-	def get_overlap_count(time_logs):
-		count = 1
+	def has_overlap(self, production_capacity, time_logs):
+		overlap = False
+		if production_capacity == 1 and len(time_logs) >= 1:
+			return True
+		if not len(time_logs):
+			return False
 
-		# Check overlap exists or not between the overlapping time logs with the current Job Card
-		for idx, row in enumerate(time_logs):
-			next_idx = idx
-			if idx + 1 < len(time_logs):
-				next_idx = idx + 1
-				next_row = time_logs[next_idx]
-				if row.name == next_row.name:
-					continue
-
-				if (
-					(
-						get_datetime(next_row.from_time) >= get_datetime(row.from_time)
-						and get_datetime(next_row.from_time) <= get_datetime(row.to_time)
-					)
-					or (
-						get_datetime(next_row.to_time) >= get_datetime(row.from_time)
-						and get_datetime(next_row.to_time) <= get_datetime(row.to_time)
-					)
-					or (
-						get_datetime(next_row.from_time) <= get_datetime(row.from_time)
-						and get_datetime(next_row.to_time) >= get_datetime(row.to_time)
-					)
-				):
-					count += 1
-
-		return count
+		# sorting overlapping job cards as per from_time
+		time_logs = sorted(time_logs, key=lambda x: x.get("from_time"))
+		# alloted_capacity has key number starting from 1. Key number will increment by 1 if non sequential job card found
+		# if key number reaches/crosses to production_capacity means capacity is full and overlap error generated
+		# this will store last to_time of sequential job cards
+		alloted_capacity = {1: time_logs[0]["to_time"]}
+		# flag for sequential Job card found
+		sequential_job_card_found = False
+		for i in range(1, len(time_logs)):
+			# scanning for all Existing keys
+			for key in alloted_capacity.keys():
+				# if current Job Card from time is greater than last to_time in that key means these job card are sequential
+				if alloted_capacity[key] <= time_logs[i]["from_time"]:
+					# So update key's value with last to_time
+					alloted_capacity[key] = time_logs[i]["to_time"]
+					# flag is true as we get sequential Job Card for that key
+					sequential_job_card_found = True
+					# Immediately break so that job card to time is not added with any other key except this
+					break
+			# if sequential job card not found above means it is overlapping  so increment key number to alloted_capacity
+			if not sequential_job_card_found:
+				# increment key number
+				key = key + 1
+				# for that key last to time is assigned.
+				alloted_capacity[key] = time_logs[i]["to_time"]
+		if len(alloted_capacity) >= production_capacity:
+			# if number of keys greater or equal to production caoacity means full capacity is utilized and we should throw overlap error
+			return True
+		return overlap
 
 	def get_time_logs(self, args, doctype, check_next_available_slot=False):
 		jc = frappe.qb.DocType("Job Card")
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index dd102b0..cd92263 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -305,6 +305,8 @@
 			frappe.throw(__("Select the Warehouse"));
 		}
 
+		frm.set_value("consider_minimum_order_qty", 0);
+
 		if (frm.doc.ignore_existing_ordered_qty) {
 			frm.events.get_items_for_material_requests(frm);
 		} else {
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 49386c4..257b60c 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -48,6 +48,7 @@
   "material_request_planning",
   "include_non_stock_items",
   "include_subcontracted_items",
+  "consider_minimum_order_qty",
   "include_safety_stock",
   "ignore_existing_ordered_qty",
   "column_break_25",
@@ -423,13 +424,19 @@
    "fieldtype": "Link",
    "label": "Sub Assembly Warehouse",
    "options": "Warehouse"
+  },
+  {
+   "default": "0",
+   "fieldname": "consider_minimum_order_qty",
+   "fieldtype": "Check",
+   "label": "Consider Minimum Order Qty"
   }
  ],
  "icon": "fa fa-calendar",
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-11-03 14:08:11.928027",
+ "modified": "2023-12-26 16:31:13.740777",
  "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 955821f..2bfd4be 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -67,6 +67,7 @@
 		combine_items: DF.Check
 		combine_sub_items: DF.Check
 		company: DF.Link
+		consider_minimum_order_qty: DF.Check
 		customer: DF.Link | None
 		for_warehouse: DF.Link | None
 		from_date: DF.Date | None
@@ -583,6 +584,7 @@
 
 		if close:
 			self.db_set("status", "Closed")
+			self.update_bin_qty()
 			return
 
 		if self.total_produced_qty > 0:
@@ -597,6 +599,9 @@
 		if close is not None:
 			self.db_set("status", self.status)
 
+		if self.docstatus == 1 and self.status != "Completed":
+			self.update_bin_qty()
+
 	def update_ordered_status(self):
 		update_status = False
 		for d in self.po_items:
@@ -815,7 +820,7 @@
 			key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "")
 			schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days))
 
-			if not key in material_request_map:
+			if key not in material_request_map:
 				# make a new MR for the combination
 				material_request_map[key] = frappe.new_doc("Material Request")
 				material_request = material_request_map[key]
@@ -1207,7 +1212,14 @@
 
 
 def get_material_request_items(
-	row, sales_order, company, ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict
+	doc,
+	row,
+	sales_order,
+	company,
+	ignore_existing_ordered_qty,
+	include_safety_stock,
+	warehouse,
+	bin_dict,
 ):
 	total_qty = row["qty"]
 
@@ -1216,8 +1228,14 @@
 		required_qty = total_qty
 	elif total_qty > bin_dict.get("projected_qty", 0):
 		required_qty = total_qty - bin_dict.get("projected_qty", 0)
-	if required_qty > 0 and required_qty < row["min_order_qty"]:
+
+	if (
+		doc.get("consider_minimum_order_qty")
+		and required_qty > 0
+		and required_qty < row["min_order_qty"]
+	):
 		required_qty = row["min_order_qty"]
+
 	item_group_defaults = get_item_group_defaults(row.item_code, company)
 
 	if not row["purchase_uom"]:
@@ -1555,6 +1573,7 @@
 
 			if details.qty > 0:
 				items = get_material_request_items(
+					doc,
 					details,
 					sales_order,
 					company,
@@ -1597,19 +1616,23 @@
 	)
 
 	locations = get_available_item_locations(
-		item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True
+		item.get("item_code"),
+		warehouses,
+		item.get("quantity") * item.get("conversion_factor"),
+		company,
+		ignore_validation=True,
 	)
 
 	required_qty = item.get("quantity")
+	if item.get("conversion_factor") and item.get("purchase_uom") != item.get("stock_uom"):
+		# Convert qty to stock UOM
+		required_qty = required_qty * item.get("conversion_factor")
+
 	# get available material by transferring to production warehouse
 	for d in locations:
 		if required_qty <= 0:
 			return
 
-		conversion_factor = 1.0
-		if purchase_uom != stock_uom and purchase_uom == item["uom"]:
-			conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"])
-
 		new_dict = copy.deepcopy(item)
 		quantity = required_qty if d.get("qty") > required_qty else d.get("qty")
 
@@ -1619,10 +1642,11 @@
 				"material_request_type": "Material Transfer",
 				"uom": new_dict.get("stock_uom"),  # internal transfer should be in stock UOM
 				"from_warehouse": d.get("warehouse"),
+				"conversion_factor": 1.0,
 			}
 		)
 
-		required_qty -= quantity / conversion_factor
+		required_qty -= quantity
 		new_mr_items.append(new_dict)
 
 	# raise purchase request for remaining qty
@@ -1634,7 +1658,7 @@
 		if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"):
 			required_qty = ceil(required_qty)
 
-		item["quantity"] = required_qty
+		item["quantity"] = required_qty / item.get("conversion_factor")
 
 		new_mr_items.append(item)
 
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index dd32c34..cb99b88 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1283,12 +1283,14 @@
 		for row in items:
 			row = frappe._dict(row)
 			if row.material_request_type == "Material Transfer":
+				self.assertTrue(row.uom == row.stock_uom)
 				self.assertTrue(row.from_warehouse in [wh1, wh2])
 				self.assertEqual(row.quantity, 2)
 
 			if row.material_request_type == "Purchase":
+				self.assertTrue(row.uom != row.stock_uom)
 				self.assertTrue(row.warehouse == mrp_warhouse)
-				self.assertEqual(row.quantity, 12)
+				self.assertEqual(row.quantity, 12.0)
 
 	def test_mr_qty_for_same_rm_with_different_sub_assemblies(self):
 		from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
@@ -1404,6 +1406,122 @@
 
 		self.assertEqual(after_qty, before_qty)
 
+	def test_material_request_qty_purchase_and_material_transfer(self):
+		from erpnext.stock.doctype.item.test_item import make_item
+		from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+		fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
+		bom_item = make_item(
+			properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"}
+		).name
+
+		store_warehouse = create_warehouse("Store Warehouse", company="_Test Company")
+		rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
+
+		make_stock_entry(
+			item_code=bom_item,
+			qty=60,
+			target=store_warehouse,
+			rate=99,
+		)
+
+		if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}):
+			doc = frappe.get_doc("Item", bom_item)
+			doc.append("uoms", {"uom": "Nos", "conversion_factor": 10})
+			doc.save()
+
+		make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
+
+		pln = create_production_plan(
+			item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1
+		)
+
+		pln.for_warehouse = rm_warehouse
+		items = get_items_for_material_requests(
+			pln.as_dict(), warehouses=[{"warehouse": store_warehouse}]
+		)
+
+		for row in items:
+			self.assertEqual(row.get("quantity"), 10.0)
+			self.assertEqual(row.get("material_request_type"), "Material Transfer")
+			self.assertEqual(row.get("uom"), "_Test UOM 1")
+			self.assertEqual(row.get("from_warehouse"), store_warehouse)
+			self.assertEqual(row.get("conversion_factor"), 1.0)
+
+		items = get_items_for_material_requests(
+			pln.as_dict(), warehouses=[{"warehouse": pln.for_warehouse}]
+		)
+
+		for row in items:
+			self.assertEqual(row.get("quantity"), 1.0)
+			self.assertEqual(row.get("material_request_type"), "Purchase")
+			self.assertEqual(row.get("uom"), "Nos")
+			self.assertEqual(row.get("conversion_factor"), 10.0)
+
+	def test_unreserve_qty_on_closing_of_pp(self):
+		from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+		from erpnext.stock.utils import get_or_make_bin
+
+		fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
+		rm_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
+
+		store_warehouse = create_warehouse("Store Warehouse", company="_Test Company")
+		rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
+
+		make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC")
+
+		pln = create_production_plan(
+			item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1
+		)
+
+		pln.for_warehouse = rm_warehouse
+		mr_items = get_items_for_material_requests(pln.as_dict())
+		for d in mr_items:
+			pln.append("mr_items", d)
+
+		pln.save()
+		pln.submit()
+
+		bin_name = get_or_make_bin(rm_item, rm_warehouse)
+		before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+
+		pln.reload()
+		pln.set_status(close=True)
+
+		bin_name = get_or_make_bin(rm_item, rm_warehouse)
+		after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+		self.assertAlmostEqual(after_qty, before_qty - 10)
+
+		pln.reload()
+		pln.set_status(close=False)
+
+		bin_name = get_or_make_bin(rm_item, rm_warehouse)
+		after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+		self.assertAlmostEqual(after_qty, before_qty)
+
+	def test_min_order_qty_in_pp(self):
+		from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+		from erpnext.stock.utils import get_or_make_bin
+
+		fg_item = make_item(properties={"is_stock_item": 1}).name
+		rm_item = make_item(properties={"is_stock_item": 1, "min_order_qty": 1000}).name
+
+		rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
+
+		make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC")
+
+		pln = create_production_plan(item_code=fg_item, planned_qty=10, do_not_submit=1)
+
+		pln.for_warehouse = rm_warehouse
+		mr_items = get_items_for_material_requests(pln.as_dict())
+		for d in mr_items:
+			self.assertEqual(d.get("quantity"), 10.0)
+
+		pln.consider_minimum_order_qty = 1
+		mr_items = get_items_for_material_requests(pln.as_dict())
+		for d in mr_items:
+			self.assertEqual(d.get("quantity"), 1000.0)
+
 
 def create_production_plan(**args):
 	"""
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 78bfc76..0acc2b1 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -930,7 +930,7 @@
 			validate_end_of_life(self.production_item)
 
 	def validate_qty(self):
-		if not self.qty > 0:
+		if self.qty <= 0:
 			frappe.throw(_("Quantity to Manufacture must be greater than 0."))
 
 		if (
@@ -957,7 +957,7 @@
 
 			max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0)
 
-			if not max_qty > 0:
+			if max_qty <= 0:
 				frappe.throw(
 					_("Cannot produce more item for {0}").format(self.production_item), OverProductionError
 				)
@@ -968,7 +968,7 @@
 				)
 
 	def validate_transfer_against(self):
-		if not self.docstatus == 1:
+		if self.docstatus != 1:
 			# let user configure operations until they're ready to submit
 			return
 		if not self.operations:
@@ -981,7 +981,7 @@
 
 	def validate_operation_time(self):
 		for d in self.operations:
-			if not d.time_in_mins > 0:
+			if d.time_in_mins <= 0:
 				frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
 
 	def update_required_items(self):
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 0de100a..7ade21d 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -351,5 +351,9 @@
 erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation
 erpnext.patches.v14_0.update_zero_asset_quantity_field
 execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
+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
 # 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
diff --git a/erpnext/patches/v12_0/set_task_status.py b/erpnext/patches/v12_0/set_task_status.py
index 1c6654e..27810d7 100644
--- a/erpnext/patches/v12_0/set_task_status.py
+++ b/erpnext/patches/v12_0/set_task_status.py
@@ -10,7 +10,7 @@
 	)
 	if property_setter_name:
 		property_setter = frappe.get_doc("Property Setter", property_setter_name)
-		if not "Completed" in property_setter.value:
+		if "Completed" not in property_setter.value:
 			property_setter.value = property_setter.value + "\nCompleted"
 			property_setter.save()
 
diff --git a/erpnext/patches/v13_0/update_sla_enhancements.py b/erpnext/patches/v13_0/update_sla_enhancements.py
index 84c683a..cf9e185 100644
--- a/erpnext/patches/v13_0/update_sla_enhancements.py
+++ b/erpnext/patches/v13_0/update_sla_enhancements.py
@@ -46,7 +46,7 @@
 					{"response_time": response_time, "resolution_time": resolution_time},
 				)
 			if priority.parenttype == "Service Level":
-				if not priority.parent in priority_dict:
+				if priority.parent not in priority_dict:
 					priority_dict[priority.parent] = []
 				priority_dict[priority.parent].append(priority)
 
diff --git a/erpnext/patches/v14_0/set_maintain_stock_for_bom_item.py b/erpnext/patches/v14_0/set_maintain_stock_for_bom_item.py
new file mode 100644
index 0000000..f0b618f
--- /dev/null
+++ b/erpnext/patches/v14_0/set_maintain_stock_for_bom_item.py
@@ -0,0 +1,19 @@
+import frappe
+
+
+def execute():
+	if not frappe.db.exists("BOM", {"docstatus": 1}):
+		return
+
+	# Added is_stock_item to handle Read Only based on condition for the rate field
+	frappe.db.sql(
+		"""
+		UPDATE
+			`tabBOM Item` boi,
+			`tabItem` i
+		SET
+			boi.is_stock_item = i.is_stock_item
+		WHERE
+			boi.item_code = i.name
+	"""
+	)
diff --git a/erpnext/patches/v14_0/update_total_asset_cost_field.py b/erpnext/patches/v14_0/update_total_asset_cost_field.py
new file mode 100644
index 0000000..57cf71b
--- /dev/null
+++ b/erpnext/patches/v14_0/update_total_asset_cost_field.py
@@ -0,0 +1,17 @@
+import frappe
+
+
+def execute():
+	asset = frappe.qb.DocType("Asset")
+	frappe.qb.update(asset).set(asset.total_asset_cost, asset.gross_purchase_amount).run()
+
+	asset_repair_list = frappe.db.get_all(
+		"Asset Repair",
+		filters={"docstatus": 1, "repair_status": "Completed", "capitalize_repair_cost": 1},
+		fields=["asset", "repair_cost"],
+	)
+
+	for asset_repair in asset_repair_list:
+		frappe.qb.update(asset).set(
+			asset.total_asset_cost, asset.total_asset_cost + asset_repair.repair_cost
+		).where(asset.name == asset_repair.asset).run()
diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
index 793497b..ddce997 100644
--- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
+++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
@@ -3,6 +3,7 @@
 
 def execute():
 	frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule")
+	frappe.reload_doc("assets", "doctype", "Asset Finance Book")
 
 	assets = get_details_of_draft_or_submitted_depreciable_assets()
 
@@ -86,6 +87,7 @@
 			afb.frequency_of_depreciation,
 			afb.rate_of_depreciation,
 			afb.expected_value_after_useful_life,
+			afb.daily_prorata_based,
 			afb.shift_based,
 		)
 		.where(asset.docstatus < 2)
diff --git a/erpnext/portal/doctype/homepage/homepage.js b/erpnext/portal/doctype/homepage/homepage.js
index 6797904..6739979 100644
--- a/erpnext/portal/doctype/homepage/homepage.js
+++ b/erpnext/portal/doctype/homepage/homepage.js
@@ -2,14 +2,6 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Homepage', {
-	setup: function(frm) {
-		frm.fields_dict["products"].grid.get_field("item").get_query = function() {
-			return {
-				filters: {'published': 1}
-			}
-		}
-	},
-
 	refresh: function(frm) {
 		frm.add_custom_button(__('Set Meta Tags'), () => {
 			frappe.utils.set_meta_tag('home');
diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py
index 903d4a6..86426b2 100644
--- a/erpnext/portal/utils.py
+++ b/erpnext/portal/utils.py
@@ -50,7 +50,7 @@
 	party = frappe.new_doc(doctype)
 	fullname = frappe.utils.get_fullname(user)
 
-	if not doctype == "Customer":
+	if doctype != "Customer":
 		party.update(
 			{
 				"supplier_name": fullname,
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 4f2e395..751dcbd 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -661,7 +661,7 @@
 	"""
 	set status for project and all related tasks
 	"""
-	if not status in ("Completed", "Cancelled"):
+	if status not in ("Completed", "Cancelled"):
 		frappe.throw(_("Status must be Cancelled or Completed"))
 
 	project = frappe.get_doc("Project", project)
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 0860d9c..77ecf75 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -36,14 +36,14 @@
 
 				// no idea where me is coming from
 				if(this.frm.get_field('shipping_address')) {
-					this.frm.set_query("shipping_address", function() {
-						if(me.frm.doc.customer) {
+					this.frm.set_query("shipping_address", () => {
+						if(this.frm.doc.customer) {
 							return {
 								query: 'frappe.contacts.doctype.address.address.address_query',
-								filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer }
+								filters: { link_doctype: 'Customer', link_name: this.frm.doc.customer }
 							};
 						} else
-							return erpnext.queries.company_address_query(me.frm.doc)
+							return erpnext.queries.company_address_query(this.frm.doc)
 					});
 				}
 			}
@@ -361,9 +361,14 @@
 								new erpnext.SerialBatchPackageSelector(
 									me.frm, item, (r) => {
 										if (r) {
+											let qty = Math.abs(r.total_qty);
+											if (doc.is_return) {
+												qty = qty * -1;
+											}
+
 											let update_values = {
 												"serial_and_batch_bundle": r.name,
-												"qty": Math.abs(r.total_qty)
+												"qty": qty
 											}
 
 											if (r.warehouse) {
@@ -396,9 +401,14 @@
 								new erpnext.SerialBatchPackageSelector(
 									me.frm, item, (r) => {
 										if (r) {
+											let qty = Math.abs(r.total_qty);
+											if (doc.is_return) {
+												qty = qty * -1;
+											}
+
 											let update_values = {
 												"serial_and_batch_bundle": r.name,
-												"rejected_qty": Math.abs(r.total_qty)
+												"rejected_qty": qty
 											}
 
 											if (r.warehouse) {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 6dc24fa..3935783 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -357,7 +357,7 @@
 
 	onload_post_render() {
 		if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length
-			&& !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) {
+			&& !this.frm.doc.__onload?.load_after_mapping) {
 			frappe.after_ajax(() => this.apply_default_taxes());
 		} else if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"]
 			&& !this.frm.doc.is_pos) {
@@ -380,6 +380,7 @@
 	}
 
 	scan_barcode() {
+		frappe.flags.dialog_set = false;
 		const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
 		barcode_scanner.process_scan();
 	}
@@ -964,9 +965,9 @@
 		let me = this;
 		this.set_dynamic_labels();
 		let company_currency = this.get_company_currency();
-		// Added `ignore_price_list` to determine if document is loading after mapping from another doc
+		// Added `load_after_mapping` to determine if document is loading after mapping from another doc
 		if(this.frm.doc.currency && this.frm.doc.currency !== company_currency
-				&& !(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list)) {
+				&& !this.frm.doc.__onload?.load_after_mapping) {
 
 			this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency,
 				function(exchange_rate) {
@@ -998,7 +999,7 @@
 		}
 
 		if(flt(this.frm.doc.conversion_rate)>0.0) {
-			if(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) {
+			if(this.frm.doc.__onload?.load_after_mapping) {
 				this.calculate_taxes_and_totals();
 			} else if (!this.in_apply_price_list){
 				this.apply_price_list();
@@ -1085,9 +1086,9 @@
 		this.set_dynamic_labels();
 
 		var company_currency = this.get_company_currency();
-		// Added `ignore_price_list` to determine if document is loading after mapping from another doc
+		// Added `load_after_mapping` to determine if document is loading after mapping from another doc
 		if(this.frm.doc.price_list_currency !== company_currency  &&
-				!(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list)) {
+				!this.frm.doc.__onload?.load_after_mapping) {
 			this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.price_list_currency, company_currency,
 				function(exchange_rate) {
 					me.frm.set_value("plc_conversion_rate", exchange_rate);
@@ -1476,7 +1477,7 @@
 		}
 
 		// Target doc created from a mapped doc
-		if (this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) {
+		if (this.frm.doc.__onload?.load_after_mapping) {
 			// Calculate totals even though pricing rule is not applied.
 			// `apply_pricing_rule` is triggered due to change in data which most likely contributes to Total.
 			if (calculate_taxes_and_totals) me.calculate_taxes_and_totals();
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 1b10d8a..17341d1 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -2,10 +2,16 @@
 
 erpnext.financial_statements = {
 	"filters": get_filters(),
-	"formatter": function(value, row, column, data, default_formatter) {
+	"formatter": function(value, row, column, data, default_formatter, filter) {
 		if (data && column.fieldname=="account") {
 			value = data.account_name || value;
 
+			if (filter && filter?.text && filter?.type == "contains") {
+				if (!value.toLowerCase().includes(filter.text)) {
+					return value;
+				}
+			}
+
 			if (data.account) {
 				column.link_onclick =
 					"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 25fc754..b0ea568 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -8,7 +8,7 @@
 		if(!company && cur_frm)
 			company = cur_frm.doc.company;
 		if(company)
-			return frappe.get_doc(":Company", company).default_currency || frappe.boot.sysdefaults.currency;
+			return frappe.get_doc(":Company", company)?.default_currency || frappe.boot.sysdefaults.currency;
 		else
 			return frappe.boot.sysdefaults.currency;
 	},
@@ -1077,7 +1077,7 @@
 }
 
 function get_time_left(timestamp, agreement_status) {
-	const diff = moment(timestamp).diff(moment());
+	const diff = moment(timestamp).diff(frappe.datetime.system_datetime(true));
 	const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : 'Failed';
 	let indicator = (diff_display == 'Failed' && agreement_status != 'Fulfilled') ? 'red' : 'green';
 	return {'diff_display': diff_display, 'indicator': indicator};
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index a4f74bd..a1ebfe9 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -114,13 +114,13 @@
 
 			frappe.run_serially([
 				() => this.set_selector_trigger_flag(data),
+				() => this.set_serial_no(row, serial_no),
+				() => this.set_batch_no(row, 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.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)
@@ -131,10 +131,10 @@
 	// 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;
+		const {has_batch_no, has_serial_no} = data;
 
-		const require_selecting_batch = has_batch_no && !batch_no;
-		const require_selecting_serial = has_serial_no && !serial_no;
+		const require_selecting_batch = has_batch_no;
+		const require_selecting_serial = has_serial_no;
 
 		if (!(require_selecting_batch || require_selecting_serial)) {
 			frappe.flags.hide_serial_batch_dialog = true;
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index 5514963..b92b02e 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -184,6 +184,12 @@
 				refresh_field("incentives",row.name,row.parentfield);
 			}
 
+			warehouse(doc, cdt, cdn) {
+				if (doc.docstatus === 0 && doc.is_return && !doc.return_against) {
+					frappe.model.set_value(cdt, cdn, "incoming_rate", 0.0);
+				}
+			}
+
 			toggle_editable_price_list_rate() {
 				var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name);
 				var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
@@ -317,9 +323,14 @@
 								new erpnext.SerialBatchPackageSelector(
 									me.frm, item, (r) => {
 										if (r) {
+											let qty = Math.abs(r.total_qty);
+											if (doc.is_return) {
+												qty = qty * -1;
+											}
+
 											frappe.model.set_value(item.doctype, item.name, {
 												"serial_and_batch_bundle": r.name,
-												"qty": Math.abs(r.total_qty)
+												"qty": qty
 											});
 										}
 									}
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 9267801..4cd1243 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -31,8 +31,40 @@
 			secondary_action: () => this.edit_full_form(),
 		});
 
-		this.dialog.set_value("qty", this.item.qty);
 		this.dialog.show();
+		this.$scan_btn = this.dialog.$wrapper.find(".link-btn");
+		this.$scan_btn.css("display", "inline");
+
+		let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty;
+
+		if (this.item?.is_rejected) {
+			qty = this.item.rejected_qty;
+		}
+
+		qty = Math.abs(qty);
+		if (qty > 0) {
+			this.dialog.set_value("qty", qty).then(() => {
+				if (this.item.serial_no && !this.item.serial_and_batch_bundle) {
+					let serial_nos = this.item.serial_no.split('\n');
+					if (serial_nos.length > 1) {
+						serial_nos.forEach(serial_no => {
+							this.dialog.fields_dict.entries.df.data.push({
+								serial_no: serial_no,
+								batch_no: this.item.batch_no
+							});
+						});
+					} else {
+						this.dialog.set_value("scan_serial_no", this.item.serial_no);
+					}
+					frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
+				} else if (this.item.batch_no && !this.item.serial_and_batch_bundle) {
+					this.dialog.set_value("scan_batch_no", this.item.batch_no);
+					frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', '');
+				}
+
+				this.dialog.fields_dict.entries.grid.refresh();
+			});
+		}
 	}
 
 	get_serial_no_filters() {
@@ -95,6 +127,7 @@
 		if (this.item.has_serial_no) {
 			fields.push({
 				fieldtype: 'Data',
+				options: 'Barcode',
 				fieldname: 'scan_serial_no',
 				label: __('Scan Serial No'),
 				get_query: () => {
@@ -106,15 +139,10 @@
 			});
 		}
 
-		if (this.item.has_batch_no && this.item.has_serial_no) {
-			fields.push({
-				fieldtype: 'Column Break',
-			});
-		}
-
-		if (this.item.has_batch_no) {
+		if (this.item.has_batch_no && !this.item.has_serial_no) {
 			fields.push({
 				fieldtype: 'Data',
+				options: 'Barcode',
 				fieldname: 'scan_batch_no',
 				label: __('Scan Batch No'),
 				onchange: () => this.update_serial_batch_no()
@@ -309,6 +337,14 @@
 	}
 
 	get_auto_data() {
+		if (this.item.serial_and_batch_bundle || this.item.rejected_serial_and_batch_bundle) {
+			return;
+		}
+
+		if (this.item.serial_no || this.item.batch_no) {
+			return;
+		}
+
 		let { qty, based_on } = this.dialog.get_values();
 
 		if (!based_on) {
@@ -340,16 +376,57 @@
 		const { scan_serial_no, scan_batch_no } = this.dialog.get_values();
 
 		if (scan_serial_no) {
-			this.dialog.fields_dict.entries.df.data.push({
-				serial_no: scan_serial_no
+			let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => {
+				if (d.serial_no === scan_serial_no) {
+					return d
+				}
 			});
 
-			this.dialog.fields_dict.scan_serial_no.set_value('');
+			if (existing_row?.length) {
+				frappe.throw(__('Serial No {0} already exists', [scan_serial_no]));
+			}
+
+			if (!this.item.has_batch_no) {
+				this.dialog.fields_dict.entries.df.data.push({
+					serial_no: scan_serial_no
+				});
+
+				this.dialog.fields_dict.scan_serial_no.set_value('');
+			} else {
+				frappe.call({
+					method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_batch_no_from_serial_no',
+					args: {
+						serial_no: scan_serial_no,
+					},
+					callback: (r) => {
+						if (r.message) {
+							this.dialog.fields_dict.entries.df.data.push({
+								serial_no: scan_serial_no,
+								batch_no: r.message
+							});
+
+							this.dialog.fields_dict.scan_serial_no.set_value('');
+						}
+					}
+
+				})
+			}
 		} else if (scan_batch_no) {
-			this.dialog.fields_dict.entries.df.data.push({
-				batch_no: scan_batch_no
+			let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => {
+				if (d.batch_no === scan_batch_no) {
+					return d
+				}
 			});
 
+			if (existing_row?.length) {
+				existing_row[0].qty += 1;
+			} else {
+				this.dialog.fields_dict.entries.df.data.push({
+					batch_no: scan_batch_no,
+					qty: 1
+				});
+			}
+
 			this.dialog.fields_dict.scan_batch_no.set_value('');
 		}
 
@@ -407,13 +484,13 @@
 	}
 
 	render_data() {
-		if (!this.frm.is_new() && this.bundle) {
+		if (this.bundle) {
 			frappe.call({
 				method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_serial_batch_ledgers',
 				args: {
 					item_code: this.item.item_code,
 					name: this.bundle,
-					voucher_no: this.item.parent,
+					voucher_no: !this.frm.is_new() ? this.item.parent : "",
 				}
 			}).then(r => {
 				if (r.message) {
@@ -425,6 +502,7 @@
 
 	set_data(data) {
 		data.forEach(d => {
+			d.qty = Math.abs(d.qty);
 			this.dialog.fields_dict.entries.df.data.push(d);
 		});
 
diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py
index efeaeed..250a4b1 100644
--- a/erpnext/regional/united_arab_emirates/utils.py
+++ b/erpnext/regional/united_arab_emirates/utils.py
@@ -153,7 +153,9 @@
 					"account": tax.account_head,
 					"cost_center": tax.cost_center,
 					"posting_date": doc.posting_date,
+					"against_type": "Supplier",
 					"against": doc.supplier,
+					"against_link": doc.supplier,
 					dr_or_cr: tax.base_tax_amount_after_discount_amount,
 					dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount
 					if account_currency == doc.company_currency
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 3b97123..31bbbcf 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -665,7 +665,7 @@
  "search_fields": "customer_name,customer_group,territory, mobile_no,primary_address",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
- "sort_order": "ASC",
+ "sort_order": "DESC",
  "states": [],
  "title_field": "customer_name",
  "track_changes": 1
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 96df0ed..efb9820 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -587,7 +587,8 @@
 		"""
 		select sum(debit) - sum(credit)
 		from `tabGL Entry` where party_type = 'Customer'
-		and party = %s and company=%s {0}""".format(
+		and is_cancelled = 0 and party = %s
+		and company=%s {0}""".format(
 			cond
 		),
 		(customer, company),
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 8d3bb78..00b79e3 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -434,9 +434,6 @@
 		ignore_permissions=ignore_permissions,
 	)
 
-	# postprocess: fetch shipping address, set missing values
-	doclist.set_onload("ignore_price_list", True)
-
 	return doclist
 
 
@@ -505,8 +502,6 @@
 		ignore_permissions=ignore_permissions,
 	)
 
-	doclist.set_onload("ignore_price_list", True)
-
 	return doclist
 
 
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 97b214e..b206e3f 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -182,7 +182,7 @@
 	create_stock_reservation_entries(frm) {
 		const dialog = new frappe.ui.Dialog({
 			title: __("Stock Reservation"),
-			size: "large",
+			size: "extra-large",
 			fields: [
 				{
 					fieldname: "set_warehouse",
@@ -207,6 +207,50 @@
 					},
 				},
 				{fieldtype: "Column Break"},
+				{
+					fieldname: "add_item",
+					fieldtype: "Link",
+					label: __("Add Item"),
+					options: "Sales Order Item",
+					get_query: () => {
+						return {
+							query: "erpnext.controllers.queries.get_filtered_child_rows",
+							filters: {
+								"parenttype": frm.doc.doctype,
+								"parent": frm.doc.name,
+								"reserve_stock": 1,
+							}
+						}
+					},
+					onchange: () => {
+						let sales_order_item = dialog.get_value("add_item");
+
+						if (sales_order_item) {
+							frm.doc.items.forEach(item => {
+								if (item.name === sales_order_item) {
+									let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor)))) / flt(item.conversion_factor);
+
+									if (unreserved_qty > 0) {
+										dialog.fields_dict.items.df.data.forEach((row) => {
+											if (row.sales_order_item === sales_order_item) {
+												unreserved_qty -= row.qty_to_reserve;
+											}
+										});
+									}
+
+									dialog.fields_dict.items.df.data.push({
+										'sales_order_item': item.name,
+										'item_code': item.item_code,
+										'warehouse': dialog.get_value("set_warehouse") || item.warehouse,
+										'qty_to_reserve': Math.max(unreserved_qty, 0)
+									});
+									dialog.fields_dict.items.grid.refresh();
+									dialog.set_value("add_item", undefined);
+								}
+							});
+						}
+					},
+				},
 				{fieldtype: "Section Break"},
 				{
 					fieldname: "items",
@@ -218,10 +262,34 @@
 					fields: [
 						{
 							fieldname: "sales_order_item",
-							fieldtype: "Data",
+							fieldtype: "Link",
 							label: __("Sales Order Item"),
+							options: "Sales Order Item",
 							reqd: 1,
-							read_only: 1,
+							in_list_view: 1,
+							get_query: () => {
+								return {
+									query: "erpnext.controllers.queries.get_filtered_child_rows",
+									filters: {
+										"parenttype": frm.doc.doctype,
+										"parent": frm.doc.name,
+										"reserve_stock": 1,
+									}
+								}
+							},
+							onchange: (event) => {
+								if (event) {
+									let name = $(event.currentTarget).closest(".grid-row").attr("data-name");
+									let item_row = dialog.fields_dict.items.grid.grid_rows_by_docname[name].doc;
+
+									frm.doc.items.forEach(item => {
+										if (item.name === item_row.sales_order_item) {
+											item_row.item_code = item.item_code;
+										}
+									});
+									dialog.fields_dict.items.grid.refresh();
+								}
+							}
 						},
 						{
 							fieldname: "item_code",
@@ -284,14 +352,14 @@
 
 		frm.doc.items.forEach(item => {
 			if (item.reserve_stock) {
-				let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor))))
+				let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor)))) / flt(item.conversion_factor);
 
 				if (unreserved_qty > 0) {
 					dialog.fields_dict.items.df.data.push({
 						'sales_order_item': item.name,
 						'item_code': item.item_code,
 						'warehouse': item.warehouse,
-						'qty_to_reserve': (unreserved_qty / flt(item.conversion_factor))
+						'qty_to_reserve': unreserved_qty
 					});
 				}
 			}
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index fd6c027..09941ea 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -995,7 +995,6 @@
 
 	# Should be called after mapping items.
 	set_missing_values(so, target_doc)
-	target_doc.set_onload("ignore_price_list", True)
 
 	return target_doc
 
@@ -1085,8 +1084,6 @@
 	if automatically_fetch_payment_terms:
 		doclist.set_payment_schedule()
 
-	doclist.set_onload("ignore_price_list", True)
-
 	return doclist
 
 
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 193048f..bd85792 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -520,7 +520,7 @@
 	}
 
 	render_taxes(taxes) {
-		if (taxes.length) {
+		if (taxes && taxes.length) {
 			const currency = this.events.get_frm().doc.currency;
 			const taxes_html = taxes.map(t => {
 				if (t.tax_amount_after_discount_amount == 0.0) return;
diff --git a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py
index a58f403..40aa9ac 100644
--- a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py
+++ b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py
@@ -3,11 +3,11 @@
 
 
 import frappe
-from frappe import _
+from frappe import _, qb
+from frappe.query_builder import Criterion
 
 from erpnext import get_default_company
 from erpnext.accounts.party import get_party_details
-from erpnext.stock.get_item_details import get_price_list_rate_for
 
 
 def execute(filters=None):
@@ -50,6 +50,42 @@
 	]
 
 
+def fetch_item_prices(
+	customer: str = None, price_list: str = None, selling_price_list: str = None, items: list = None
+):
+	price_list_map = frappe._dict()
+	ip = qb.DocType("Item Price")
+	and_conditions = []
+	or_conditions = []
+	if items:
+		and_conditions.append(ip.item_code.isin([x.item_code for x in items]))
+		and_conditions.append(ip.selling == True)
+
+		or_conditions.append(ip.customer == None)
+		or_conditions.append(ip.price_list == None)
+
+		if customer:
+			or_conditions.append(ip.customer == customer)
+
+		if price_list:
+			or_conditions.append(ip.price_list == price_list)
+
+		if selling_price_list:
+			or_conditions.append(ip.price_list == selling_price_list)
+
+		res = (
+			qb.from_(ip)
+			.select(ip.item_code, ip.price_list, ip.price_list_rate)
+			.where(Criterion.all(and_conditions))
+			.where(Criterion.any(or_conditions))
+			.run(as_dict=True)
+		)
+		for x in res:
+			price_list_map.update({(x.item_code, x.price_list): x.price_list_rate})
+
+	return price_list_map
+
+
 def get_data(filters=None):
 	data = []
 	customer_details = get_customer_details(filters)
@@ -59,9 +95,17 @@
 		"Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code"
 	)
 	item_stock_map = {item.item_code: item.available for item in item_stock_map}
+	price_list_map = fetch_item_prices(
+		customer_details.customer,
+		customer_details.price_list,
+		customer_details.selling_price_list,
+		items,
+	)
 
 	for item in items:
-		price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0
+		price_list_rate = price_list_map.get(
+			(item.item_code, customer_details.price_list or customer_details.selling_price_list), 0.0
+		)
 		available_stock = item_stock_map.get(item.item_code)
 
 		data.append(
diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index 2624db3..cd45e7d 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -240,7 +240,7 @@
 	for row in data:
 		item_key = row.get("item_code")
 
-		if not item_key in item_wise_sales_map:
+		if item_key not in item_wise_sales_map:
 			item_wise_sales_map[item_key] = 0
 
 		item_wise_sales_map[item_key] = flt(item_wise_sales_map[item_key]) + flt(row.get("amount"))
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index 2969123..1e1d0c0 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -167,7 +167,7 @@
 		if filters.get("group_by_so"):
 			so_name = row["sales_order"]
 
-			if not so_name in sales_order_map:
+			if so_name not in sales_order_map:
 				# create an entry
 				row_copy = copy.deepcopy(row)
 				sales_order_map[so_name] = row_copy
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 cb6e8a1..9f3ba0d 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
@@ -3,7 +3,8 @@
 
 
 import frappe
-from frappe import _, msgprint
+from frappe import _, msgprint, qb
+from frappe.query_builder import Criterion
 
 from erpnext import get_company_currency
 
@@ -214,24 +215,33 @@
 	if items:
 		conditions.append("dt_item.item_code in (%s)" % ", ".join(["%s"] * len(items)))
 		values += items
+	else:
+		# return empty result, if no items are fetched after filtering on 'item group' and 'brand'
+		conditions.append("dt_item.item_code = Null")
 
 	return " and ".join(conditions), values
 
 
 def get_items(filters):
+	item = qb.DocType("Item")
+
+	item_query_conditions = []
 	if filters.get("item_group"):
-		key = "item_group"
-	elif filters.get("brand"):
-		key = "brand"
-	else:
-		key = ""
-
-	items = []
-	if key:
-		items = frappe.db.sql_list(
-			"""select name from tabItem where %s = %s""" % (key, "%s"), (filters[key])
+		# Handle 'Parent' nodes as well.
+		item_group = qb.DocType("Item Group")
+		lft, rgt = frappe.db.get_all(
+			"Item Group", filters={"name": filters.get("item_group")}, fields=["lft", "rgt"], as_list=True
+		)[0]
+		item_group_query = (
+			qb.from_(item_group)
+			.select(item_group.name)
+			.where((item_group.lft >= lft) & (item_group.rgt <= rgt))
 		)
+		item_query_conditions.append(item.item_group.isin(item_group_query))
+	if filters.get("brand"):
+		item_query_conditions.append(item.brand == filters.get("brand"))
 
+	items = qb.from_(item).select(item.name).where(Criterion.all(item_query_conditions)).run()
 	return items
 
 
diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py
index 926283f..4bc98b9 100644
--- a/erpnext/setup/demo.py
+++ b/erpnext/setup/demo.py
@@ -112,9 +112,9 @@
 	warehouse = get_warehouse(company)
 
 	if document_type == "Purchase Order":
-		posting_date = get_random_date(start_date, 1, 30)
+		posting_date = get_random_date(start_date, 1, 25)
 	else:
-		posting_date = get_random_date(start_date, 31, 364)
+		posting_date = get_random_date(start_date, 31, 350)
 
 	doctype.update(
 		{
diff --git a/erpnext/setup/demo_data/journal_entry.json b/erpnext/setup/demo_data/journal_entry.json
index b751c7c..a681be4 100644
--- a/erpnext/setup/demo_data/journal_entry.json
+++ b/erpnext/setup/demo_data/journal_entry.json
@@ -4,22 +4,22 @@
         "cheque_no": "33",
         "doctype": "Journal Entry",
         "accounts": [
-         {
-          "party_type": "Customer",
-          "party": "ABC Enterprises",
-          "credit_in_account_currency": 40000.0,
-          "debit_in_account_currency": 0.0,
-          "doctype": "Journal Entry Account",
-          "parentfield": "accounts",
-         },
-         {
-          "credit_in_account_currency": 0.0,
-          "debit_in_account_currency": 40000.0,
-          "doctype": "Journal Entry Account",
-          "parentfield": "accounts",
-         }
+            {
+                "party_type": "Customer",
+                "party": "ABC Enterprises",
+                "credit_in_account_currency": 40000.0,
+                "debit_in_account_currency": 0.0,
+                "doctype": "Journal Entry Account",
+                "parentfield": "accounts"
+            },
+            {
+                "credit_in_account_currency": 0.0,
+                "debit_in_account_currency": 40000.0,
+                "doctype": "Journal Entry Account",
+                "parentfield": "accounts"
+            }
         ],
         "user_remark": "test",
         "voucher_type": "Bank Entry"
     }
-]
\ No newline at end of file
+]
diff --git a/erpnext/setup/doctype/authorization_control/authorization_control.py b/erpnext/setup/doctype/authorization_control/authorization_control.py
index feb14a8..9446fb4 100644
--- a/erpnext/setup/doctype/authorization_control/authorization_control.py
+++ b/erpnext/setup/doctype/authorization_control/authorization_control.py
@@ -185,7 +185,10 @@
 
 		# Remove user specific rules from global authorization rules
 		for r in based_on:
-			if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]:
+			if r in final_based_on and r not in [
+				"Itemwise Discount",
+				"Item Group wise Discount",
+			]:
 				final_based_on.remove(r)
 
 		# Check for authorization set on particular roles
@@ -213,7 +216,10 @@
 
 		# Remove role specific rules from global authorization rules
 		for r in based_on:
-			if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]:
+			if r in final_based_on and r not in [
+				"Itemwise Discount",
+				"Item Group wise Discount",
+			]:
 				final_based_on.remove(r)
 
 		# Check for global authorization
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 0e8ee2d..9897847 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -274,7 +274,7 @@
 						"parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr)
 						if not wh_detail["is_group"]
 						else "",
-						"warehouse_type": wh_detail["warehouse_type"] if "warehouse_type" in wh_detail else None,
+						"warehouse_type": wh_detail.get("warehouse_type"),
 					}
 				)
 				warehouse.flags.ignore_permissions = True
diff --git a/erpnext/setup/doctype/customer_group/customer_group.js b/erpnext/setup/doctype/customer_group/customer_group.js
index 3c81b02..e352818 100644
--- a/erpnext/setup/doctype/customer_group/customer_group.js
+++ b/erpnext/setup/doctype/customer_group/customer_group.js
@@ -1,21 +1,6 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-
-cur_frm.cscript.refresh = function(doc, cdt, cdn) {
-	cur_frm.cscript.set_root_readonly(doc);
-}
-
-cur_frm.cscript.set_root_readonly = function(doc) {
-	// read-only for root customer group
-	if(!doc.parent_customer_group && !doc.__islocal) {
-		cur_frm.set_read_only();
-		cur_frm.set_intro(__("This is a root customer group and cannot be edited."));
-	} else {
-		cur_frm.set_intro(null);
-	}
-}
-
 frappe.ui.form.on("Customer Group", {
 	setup: function(frm){
 		frm.set_query('parent_customer_group', function (doc) {
@@ -48,5 +33,17 @@
 				}
 			}
 		});
-	}
+	},
+	refresh: function(frm) {
+		frm.trigger("set_root_readonly");
+	},
+	set_root_readonly: function(frm) {
+		// read-only for root customer group
+		if(!frm.doc.parent_customer_group && !frm.doc.__islocal) {
+			frm.set_read_only();
+			frm.set_intro(__("This is a root customer group and cannot be edited."));
+		} else {
+			frm.set_intro(null);
+		}
+	},
 });
diff --git a/erpnext/setup/doctype/department/department.py b/erpnext/setup/doctype/department/department.py
index 6b090f8..16f6fbf 100644
--- a/erpnext/setup/doctype/department/department.py
+++ b/erpnext/setup/doctype/department/department.py
@@ -44,7 +44,7 @@
 
 	def before_rename(self, old, new, merge=False):
 		# renaming consistency with abbreviation
-		if not frappe.get_cached_value("Company", self.company, "abbr") in new:
+		if frappe.get_cached_value("Company", self.company, "abbr") not in new:
 			new = get_abbreviated_name(new, self.company)
 
 		return new
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index 22bdf50..4b07056 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -689,7 +689,7 @@
 		]
 
 	def get_root_type_accounts(self, root_type):
-		if not root_type in self._accounts:
+		if root_type not in self._accounts:
 			self._accounts[root_type] = [
 				d.name
 				for d in frappe.db.get_all(
diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py
index 6f9176c..4bb3539 100755
--- a/erpnext/setup/doctype/employee/employee.py
+++ b/erpnext/setup/doctype/employee/employee.py
@@ -187,7 +187,7 @@
 				throw(_("Please enter relieving date."))
 
 	def validate_for_enabled_user_id(self, enabled):
-		if not self.status == "Active":
+		if self.status != "Active":
 			return
 
 		if enabled is None:
diff --git a/erpnext/setup/doctype/sales_person/sales_person.js b/erpnext/setup/doctype/sales_person/sales_person.js
index d86a8f3..f0d9aa8 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.js
+++ b/erpnext/setup/doctype/sales_person/sales_person.js
@@ -11,6 +11,7 @@
 			frm.dashboard.add_indicator(__('Total Contribution Amount Against Invoices: {0}',
 				[format_currency(info.allocated_amount_against_invoice, info.currency)]), 'blue');
 		}
+		frm.trigger("set_root_readonly");
 	},
 
 	setup: function(frm) {
@@ -27,22 +28,18 @@
 			'Sales Order': () => frappe.new_doc("Sales Order")
 				.then(() => frm.add_child("sales_team", {"sales_person": frm.doc.name}))
 		}
+	},
+	set_root_readonly: function(frm) {
+		// read-only for root
+		if(!frm.doc.parent_sales_person && !frm.doc.__islocal) {
+			frm.set_read_only();
+			frm.set_intro(__("This is a root sales person and cannot be edited."));
+		} else {
+			frm.set_intro(null);
+		}
 	}
 });
 
-cur_frm.cscript.refresh = function(doc, cdt, cdn) {
-	cur_frm.cscript.set_root_readonly(doc);
-}
-
-cur_frm.cscript.set_root_readonly = function(doc) {
-	// read-only for root
-	if(!doc.parent_sales_person && !doc.__islocal) {
-		cur_frm.set_read_only();
-		cur_frm.set_intro(__("This is a root sales person and cannot be edited."));
-	} else {
-		cur_frm.set_intro(null);
-	}
-}
 
 //get query select sales person
 cur_frm.fields_dict['parent_sales_person'].get_query = function(doc, cdt, cdn) {
diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.js b/erpnext/setup/doctype/supplier_group/supplier_group.js
index 3362929..c697a99 100644
--- a/erpnext/setup/doctype/supplier_group/supplier_group.js
+++ b/erpnext/setup/doctype/supplier_group/supplier_group.js
@@ -1,21 +1,6 @@
 // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
-cur_frm.cscript.refresh = function(doc) {
-	cur_frm.set_intro(doc.__islocal ? "" : __("There is nothing to edit."));
-	cur_frm.cscript.set_root_readonly(doc);
-};
-
-cur_frm.cscript.set_root_readonly = function(doc) {
-	// read-only for root customer group
-	if(!doc.parent_supplier_group && !doc.__islocal) {
-		cur_frm.set_read_only();
-		cur_frm.set_intro(__("This is a root supplier group and cannot be edited."));
-	} else {
-		cur_frm.set_intro(null);
-	}
-};
-
 frappe.ui.form.on("Supplier Group", {
 	setup: function(frm){
 		frm.set_query('parent_supplier_group', function (doc) {
@@ -48,5 +33,17 @@
 				}
 			}
 		});
+	},
+	refresh: function(frm) {
+		frm.set_intro(frm.doc.__islocal ? "" : __("There is nothing to edit."));
+		frm.trigger("set_root_readonly");
+	},
+	set_root_readonly: function(frm) {
+		if(!frm.doc.parent_supplier_group && !frm.doc.__islocal) {
+			frm.trigger("set_read_only");
+			frm.set_intro(__("This is a root supplier group and cannot be edited."));
+		} else {
+			frm.set_intro(null);
+		}
 	}
 });
diff --git a/erpnext/setup/doctype/territory/territory.js b/erpnext/setup/doctype/territory/territory.js
index 3caf814..e11d20b 100644
--- a/erpnext/setup/doctype/territory/territory.js
+++ b/erpnext/setup/doctype/territory/territory.js
@@ -11,23 +11,22 @@
 				}
 			}
 		};
+	},
+	refresh: function(frm) {
+		frm.trigger("set_root_readonly");
+	},
+	set_root_readonly: function(frm) {
+		// read-only for root territory
+		if(!frm.doc.parent_territory && !frm.doc.__islocal) {
+			frm.set_read_only();
+			frm.set_intro(__("This is a root territory and cannot be edited."));
+		} else {
+			frm.set_intro(null);
+		}
 	}
+
 });
 
-cur_frm.cscript.refresh = function(doc, cdt, cdn) {
-	cur_frm.cscript.set_root_readonly(doc);
-}
-
-cur_frm.cscript.set_root_readonly = function(doc) {
-	// read-only for root territory
-	if(!doc.parent_territory && !doc.__islocal) {
-		cur_frm.set_read_only();
-		cur_frm.set_intro(__("This is a root territory and cannot be edited."));
-	} else {
-		cur_frm.set_intro(null);
-	}
-}
-
 //get query select territory
 cur_frm.fields_dict['parent_territory'].get_query = function(doc,cdt,cdn) {
 	return{
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 5b993fa..7a1d5e2 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -85,8 +85,6 @@
 			except frappe.ValidationError:
 				pass
 
-	frappe.db.set_default("date_format", "dd-mm-yyyy")
-
 	setup_currency_exchange()
 
 
@@ -197,7 +195,7 @@
 
 	for item in erpnext_navbar_items:
 		current_labels = [item.get("item_label") for item in current_navbar_items]
-		if not item.get("item_label") in current_labels:
+		if item.get("item_label") not in current_labels:
 			navbar_settings.append("help_dropdown", item)
 
 	for item in current_navbar_items:
diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py
index 49ba78c..32d92f6 100644
--- a/erpnext/setup/setup_wizard/operations/taxes_setup.py
+++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py
@@ -2,8 +2,8 @@
 # License: GNU General Public License v3. See license.txt
 
 
-import os
 import json
+import os
 
 import frappe
 from frappe import _
@@ -114,10 +114,11 @@
 				frappe.scrub(country)
 			)
 			frappe.get_attr(module_name)(country, company)
-		except Exception as e:
+		except (ImportError, AttributeError):
+			pass
+		except Exception:
 			# Log error and ignore if failed to setup regional tax settings
 			frappe.log_error("Unable to setup regional tax settings")
-			pass
 
 
 def make_taxes_and_charges_template(company_name, doctype, template):
diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py
index bdbf8b4..4b4d14f 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -75,3 +75,11 @@
 			"Sales Person Tree": {"title": "Sales Person Tree", "route": "Tree/Sales Person"},
 		}
 	)
+
+
+def bootinfo(bootinfo):
+	if bootinfo.get("user") and bootinfo["user"].get("name"):
+		bootinfo["user"]["employee"] = ""
+		employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name")
+		if employee:
+			bootinfo["user"]["employee"] = employee
diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py
index da7edbf..5a60d2f 100644
--- a/erpnext/startup/leaderboard.py
+++ b/erpnext/startup/leaderboard.py
@@ -1,5 +1,5 @@
 import frappe
-from frappe.utils import cint
+from frappe.utils.deprecations import deprecated
 
 
 def get_leaderboards():
@@ -54,12 +54,13 @@
 
 @frappe.whitelist()
 def get_all_customers(date_range, company, field, limit=None):
+	filters = [["docstatus", "=", "1"], ["company", "=", company]]
+	from_date, to_date = parse_date_range(date_range)
 	if field == "outstanding_amount":
-		filters = [["docstatus", "=", "1"], ["company", "=", company]]
-		if date_range:
-			date_range = frappe.parse_json(date_range)
-			filters.append(["posting_date", ">=", "between", [date_range[0], date_range[1]]])
-		return frappe.db.get_all(
+		if from_date and to_date:
+			filters.append(["posting_date", "between", [from_date, to_date]])
+
+		return frappe.get_list(
 			"Sales Invoice",
 			fields=["customer as name", "sum(outstanding_amount) as value"],
 			filters=filters,
@@ -69,26 +70,20 @@
 		)
 	else:
 		if field == "total_sales_amount":
-			select_field = "sum(so_item.base_net_amount)"
+			select_field = "base_net_total"
 		elif field == "total_qty_sold":
-			select_field = "sum(so_item.stock_qty)"
+			select_field = "total_qty"
 
-		date_condition = get_date_condition(date_range, "so.transaction_date")
+		if from_date and to_date:
+			filters.append(["transaction_date", "between", [from_date, to_date]])
 
-		return frappe.db.sql(
-			"""
-			select so.customer as name, {0} as value
-			FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item
-				ON so.name = so_item.parent
-			where so.docstatus = 1 {1} and so.company = %s
-			group by so.customer
-			order by value DESC
-			limit %s
-		""".format(
-				select_field, date_condition
-			),
-			(company, cint(limit)),
-			as_dict=1,
+		return frappe.get_list(
+			"Sales Order",
+			fields=["customer as name", f"sum({select_field}) as value"],
+			filters=filters,
+			group_by="customer",
+			order_by="value desc",
+			limit=limit,
 		)
 
 
@@ -96,55 +91,58 @@
 def get_all_items(date_range, company, field, limit=None):
 	if field in ("available_stock_qty", "available_stock_value"):
 		select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
-		return frappe.db.get_all(
+		results = frappe.db.get_all(
 			"Bin",
 			fields=["item_code as name", "{0} as value".format(select_field)],
 			group_by="item_code",
 			order_by="value desc",
 			limit=limit,
 		)
+		readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name"))
+		return [item for item in results if item["name"] in readable_active_items]
 	else:
 		if field == "total_sales_amount":
-			select_field = "sum(order_item.base_net_amount)"
+			select_field = "base_net_amount"
 			select_doctype = "Sales Order"
 		elif field == "total_purchase_amount":
-			select_field = "sum(order_item.base_net_amount)"
+			select_field = "base_net_amount"
 			select_doctype = "Purchase Order"
 		elif field == "total_qty_sold":
-			select_field = "sum(order_item.stock_qty)"
+			select_field = "stock_qty"
 			select_doctype = "Sales Order"
 		elif field == "total_qty_purchased":
-			select_field = "sum(order_item.stock_qty)"
+			select_field = "stock_qty"
 			select_doctype = "Purchase Order"
 
-		date_condition = get_date_condition(date_range, "sales_order.transaction_date")
+		filters = [["docstatus", "=", "1"], ["company", "=", company]]
+		from_date, to_date = parse_date_range(date_range)
+		if from_date and to_date:
+			filters.append(["transaction_date", "between", [from_date, to_date]])
 
-		return frappe.db.sql(
-			"""
-			select order_item.item_code as name, {0} as value
-			from `tab{1}` sales_order join `tab{1} Item` as order_item
-				on sales_order.name = order_item.parent
-			where sales_order.docstatus = 1
-				and sales_order.company = %s {2}
-			group by order_item.item_code
-			order by value desc
-			limit %s
-		""".format(
-				select_field, select_doctype, date_condition
-			),
-			(company, cint(limit)),
-			as_dict=1,
-		)  # nosec
+		child_doctype = f"{select_doctype} Item"
+		return frappe.get_list(
+			select_doctype,
+			fields=[
+				f"`tab{child_doctype}`.item_code as name",
+				f"sum(`tab{child_doctype}`.{select_field}) as value",
+			],
+			filters=filters,
+			order_by="value desc",
+			group_by=f"`tab{child_doctype}`.item_code",
+			limit=limit,
+		)
 
 
 @frappe.whitelist()
 def get_all_suppliers(date_range, company, field, limit=None):
+	filters = [["docstatus", "=", "1"], ["company", "=", company]]
+	from_date, to_date = parse_date_range(date_range)
+
 	if field == "outstanding_amount":
-		filters = [["docstatus", "=", "1"], ["company", "=", company]]
-		if date_range:
-			date_range = frappe.parse_json(date_range)
-			filters.append(["posting_date", "between", [date_range[0], date_range[1]]])
-		return frappe.db.get_all(
+		if from_date and to_date:
+			filters.append(["posting_date", "between", [from_date, to_date]])
+
+		return frappe.get_list(
 			"Purchase Invoice",
 			fields=["supplier as name", "sum(outstanding_amount) as value"],
 			filters=filters,
@@ -154,48 +152,40 @@
 		)
 	else:
 		if field == "total_purchase_amount":
-			select_field = "sum(purchase_order_item.base_net_amount)"
+			select_field = "base_net_total"
 		elif field == "total_qty_purchased":
-			select_field = "sum(purchase_order_item.stock_qty)"
+			select_field = "total_qty"
 
-		date_condition = get_date_condition(date_range, "purchase_order.modified")
+		if from_date and to_date:
+			filters.append(["transaction_date", "between", [from_date, to_date]])
 
-		return frappe.db.sql(
-			"""
-			select purchase_order.supplier as name, {0} as value
-			FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item`
-				as purchase_order_item ON purchase_order.name = purchase_order_item.parent
-			where
-				purchase_order.docstatus = 1
-				{1}
-				and  purchase_order.company = %s
-			group by purchase_order.supplier
-			order by value DESC
-			limit %s""".format(
-				select_field, date_condition
-			),
-			(company, cint(limit)),
-			as_dict=1,
-		)  # nosec
+		return frappe.get_list(
+			"Purchase Order",
+			fields=["supplier as name", f"sum({select_field}) as value"],
+			filters=filters,
+			group_by="supplier",
+			order_by="value desc",
+			limit=limit,
+		)
 
 
 @frappe.whitelist()
 def get_all_sales_partner(date_range, company, field, limit=None):
 	if field == "total_sales_amount":
-		select_field = "sum(`base_net_total`)"
+		select_field = "base_net_total"
 	elif field == "total_commission":
-		select_field = "sum(`total_commission`)"
+		select_field = "total_commission"
 
-	filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company}
-	if date_range:
-		date_range = frappe.parse_json(date_range)
-		filters["transaction_date"] = ["between", [date_range[0], date_range[1]]]
+	filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]]
+	from_date, to_date = parse_date_range(date_range)
+	if from_date and to_date:
+		filters.append(["transaction_date", "between", [from_date, to_date]])
 
 	return frappe.get_list(
 		"Sales Order",
 		fields=[
-			"`sales_partner` as name",
-			"{} as value".format(select_field),
+			"sales_partner as name",
+			f"sum({select_field}) as value",
 		],
 		filters=filters,
 		group_by="sales_partner",
@@ -206,27 +196,29 @@
 
 @frappe.whitelist()
 def get_all_sales_person(date_range, company, field=None, limit=0):
-	date_condition = get_date_condition(date_range, "sales_order.transaction_date")
+	filters = [
+		["docstatus", "=", "1"],
+		["company", "=", company],
+		["Sales Team", "sales_person", "is", "set"],
+	]
+	from_date, to_date = parse_date_range(date_range)
+	if from_date and to_date:
+		filters.append(["transaction_date", "between", [from_date, to_date]])
 
-	return frappe.db.sql(
-		"""
-		select sales_team.sales_person as name, sum(sales_order.base_net_total) as value
-		from `tabSales Order` as sales_order join `tabSales Team` as sales_team
-			on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order'
-		where sales_order.docstatus = 1
-			and sales_order.company = %s
-			{date_condition}
-		group by sales_team.sales_person
-		order by value DESC
-		limit %s
-	""".format(
-			date_condition=date_condition
-		),
-		(company, cint(limit)),
-		as_dict=1,
+	return frappe.get_list(
+		"Sales Order",
+		fields=[
+			"`tabSales Team`.sales_person as name",
+			"sum(`tabSales Team`.allocated_amount) as value",
+		],
+		filters=filters,
+		group_by="`tabSales Team`.sales_person",
+		order_by="value desc",
+		limit=limit,
 	)
 
 
+@deprecated
 def get_date_condition(date_range, field):
 	date_condition = ""
 	if date_range:
@@ -236,3 +228,11 @@
 			field, frappe.db.escape(from_date), frappe.db.escape(to_date)
 		)
 	return date_condition
+
+
+def parse_date_range(date_range):
+	if date_range:
+		date_range = frappe.parse_json(date_range)
+		return date_range[0], date_range[1]
+
+	return None, None
diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json
index 312470d..10d9511 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -52,8 +52,7 @@
    "oldfieldtype": "Link",
    "options": "Item",
    "read_only": 1,
-   "reqd": 1,
-   "search_index": 1
+   "reqd": 1
   },
   {
    "default": "0.00",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index b85f296..7873d3e 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -301,7 +301,8 @@
    "no_copy": 1,
    "options": "Delivery Note",
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "search_index": 1
   },
   {
    "collapsible": 1,
@@ -1401,7 +1402,7 @@
  "idx": 146,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-09-04 14:15:28.363184",
+ "modified": "2023-12-18 17:19:39.368239",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index e44736b..132f8f2 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -311,11 +311,13 @@
 			)
 
 	def set_serial_and_batch_bundle_from_pick_list(self):
+		from erpnext.stock.serial_batch_bundle import SerialBatchCreation
+
 		if not self.pick_list:
 			return
 
 		for item in self.items:
-			if item.pick_list_item:
+			if item.pick_list_item and not item.serial_and_batch_bundle:
 				filters = {
 					"item_code": item.item_code,
 					"voucher_type": "Pick List",
@@ -326,7 +328,17 @@
 				bundle_id = frappe.db.get_value("Serial and Batch Bundle", filters, "name")
 
 				if bundle_id:
-					item.serial_and_batch_bundle = bundle_id
+					cls_obj = SerialBatchCreation(
+						{
+							"type_of_transaction": "Outward",
+							"serial_and_batch_bundle": bundle_id,
+							"item_code": item.get("item_code"),
+						}
+					)
+
+					cls_obj.duplicate_package()
+
+					item.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
 
 	def validate_proj_cust(self):
 		"""check for does customer belong to same project as entered.."""
@@ -408,6 +420,7 @@
 		self.update_stock_ledger()
 
 		self.cancel_packing_slips()
+		self.update_pick_list_status()
 
 		self.make_gl_entries_on_cancel()
 		self.repost_future_sle_and_gle()
@@ -964,8 +977,6 @@
 	if automatically_fetch_payment_terms:
 		doc.set_payment_schedule()
 
-	doc.set_onload("ignore_price_list", True)
-
 	return doc
 
 
@@ -1296,7 +1307,3 @@
 	)
 
 	return doclist
-
-
-def on_doctype_update():
-	frappe.db.add_index("Delivery Note", ["customer", "is_return", "return_against"])
diff --git a/erpnext/utilities/doctype/sms_log/__init__.py b/erpnext/stock/doctype/delivery_note/patches/__init__.py
similarity index 100%
rename from erpnext/utilities/doctype/sms_log/__init__.py
rename to erpnext/stock/doctype/delivery_note/patches/__init__.py
diff --git a/erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py b/erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py
new file mode 100644
index 0000000..cc29e67
--- /dev/null
+++ b/erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py
@@ -0,0 +1,27 @@
+import click
+import frappe
+
+UNUSED_INDEXES = [
+	("Delivery Note", ["customer", "is_return", "return_against"]),
+	("Sales Invoice", ["customer", "is_return", "return_against"]),
+	("Purchase Invoice", ["supplier", "is_return", "return_against"]),
+	("Purchase Receipt", ["supplier", "is_return", "return_against"]),
+]
+
+
+def execute():
+	for doctype, index_fields in UNUSED_INDEXES:
+		table = f"tab{doctype}"
+		index_name = frappe.db.get_index_name(index_fields)
+		drop_index_if_exists(table, index_name)
+
+
+def drop_index_if_exists(table: str, index: str):
+	if not frappe.db.has_index(table, index):
+		return
+
+	try:
+		frappe.db.sql_ddl(f"ALTER TABLE `{table}` DROP INDEX `{index}`")
+		click.echo(f"✓ dropped {index} index from {table}")
+	except Exception:
+		frappe.log_error("Failed to drop index")
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 9465574..933be53 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -174,6 +174,115 @@
 		for field, value in field_values.items():
 			self.assertEqual(cstr(serial_no.get(field)), value)
 
+	def test_delivery_note_return_against_denormalized_serial_no(self):
+		from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+		frappe.flags.ignore_serial_batch_bundle_validation = True
+		sn_item = "Old Serial NO Item Return Test - 1"
+		make_item(
+			sn_item,
+			{
+				"has_serial_no": 1,
+				"serial_no_series": "OSN-.####",
+				"is_stock_item": 1,
+			},
+		)
+
+		frappe.flags.ignore_serial_batch_bundle_validation = True
+		serial_nos = [
+			"OSN-1",
+			"OSN-2",
+			"OSN-3",
+			"OSN-4",
+			"OSN-5",
+			"OSN-6",
+			"OSN-7",
+			"OSN-8",
+			"OSN-9",
+			"OSN-10",
+			"OSN-11",
+			"OSN-12",
+		]
+
+		for sn in serial_nos:
+			if not frappe.db.exists("Serial No", sn):
+				sn_doc = frappe.get_doc(
+					{
+						"doctype": "Serial No",
+						"item_code": sn_item,
+						"serial_no": sn,
+					}
+				)
+				sn_doc.insert()
+
+		warehouse = "_Test Warehouse - _TC"
+		company = frappe.db.get_value("Warehouse", warehouse, "company")
+		se_doc = make_stock_entry(
+			item_code=sn_item,
+			company=company,
+			target="_Test Warehouse - _TC",
+			qty=12,
+			basic_rate=100,
+			do_not_submit=1,
+		)
+
+		se_doc.items[0].serial_no = "\n".join(serial_nos)
+		se_doc.submit()
+
+		self.assertEqual(sorted(get_serial_nos(se_doc.items[0].serial_no)), sorted(serial_nos))
+
+		dn = create_delivery_note(
+			item_code=sn_item,
+			qty=12,
+			rate=500,
+			warehouse=warehouse,
+			company=company,
+			expense_account="Cost of Goods Sold - _TC",
+			cost_center="Main - _TC",
+			do_not_submit=1,
+		)
+
+		dn.items[0].serial_no = "\n".join(serial_nos)
+		dn.submit()
+		dn.reload()
+
+		self.assertTrue(dn.items[0].serial_no)
+
+		frappe.flags.ignore_serial_batch_bundle_validation = False
+
+		# return entry
+		dn1 = make_sales_return(dn.name)
+
+		dn1.items[0].qty = -2
+
+		bundle_doc = frappe.get_doc("Serial and Batch Bundle", dn1.items[0].serial_and_batch_bundle)
+		bundle_doc.set("entries", bundle_doc.entries[:2])
+		bundle_doc.save()
+
+		dn1.save()
+		dn1.submit()
+
+		returned_serial_nos1 = get_serial_nos_from_bundle(dn1.items[0].serial_and_batch_bundle)
+		for serial_no in returned_serial_nos1:
+			self.assertTrue(serial_no in serial_nos)
+
+		dn2 = make_sales_return(dn.name)
+
+		dn2.items[0].qty = -2
+
+		bundle_doc = frappe.get_doc("Serial and Batch Bundle", dn2.items[0].serial_and_batch_bundle)
+		bundle_doc.set("entries", bundle_doc.entries[:2])
+		bundle_doc.save()
+
+		dn2.save()
+		dn2.submit()
+
+		returned_serial_nos2 = get_serial_nos_from_bundle(dn2.items[0].serial_and_batch_bundle)
+		for serial_no in returned_serial_nos2:
+			self.assertTrue(serial_no in serial_nos)
+			self.assertFalse(serial_no in returned_serial_nos1)
+
 	def test_sales_return_for_non_bundled_items_partial(self):
 		company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
 
@@ -1266,6 +1375,109 @@
 		dn.reload()
 		self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered")
 
+	def test_sales_return_valuation_for_moving_average(self):
+		item_code = make_item(
+			"_Test Item Sales Return with MA", {"is_stock_item": 1, "valuation_method": "Moving Average"}
+		).name
+
+		make_stock_entry(
+			item_code=item_code,
+			target="_Test Warehouse - _TC",
+			qty=5,
+			basic_rate=100.0,
+			posting_date=add_days(nowdate(), -5),
+		)
+		dn = create_delivery_note(
+			item_code=item_code, qty=5, rate=500, posting_date=add_days(nowdate(), -4)
+		)
+		self.assertEqual(dn.items[0].incoming_rate, 100.0)
+
+		make_stock_entry(
+			item_code=item_code,
+			target="_Test Warehouse - _TC",
+			qty=5,
+			basic_rate=200.0,
+			posting_date=add_days(nowdate(), -3),
+		)
+		make_stock_entry(
+			item_code=item_code,
+			target="_Test Warehouse - _TC",
+			qty=5,
+			basic_rate=300.0,
+			posting_date=add_days(nowdate(), -2),
+		)
+
+		dn1 = create_delivery_note(
+			is_return=1,
+			item_code=item_code,
+			return_against=dn.name,
+			qty=-5,
+			rate=500,
+			company=dn.company,
+			expense_account="Cost of Goods Sold - _TC",
+			cost_center="Main - _TC",
+			do_not_submit=1,
+			posting_date=add_days(nowdate(), -1),
+		)
+
+		# (300 * 5) + (200 * 5) = 2500
+		# 2500 / 10 = 250
+
+		self.assertAlmostEqual(dn1.items[0].incoming_rate, 250.0)
+
+	def test_sales_return_valuation_for_moving_average_case2(self):
+		# Make DN return
+		# Make Bakcdated Purchase Receipt and check DN return valuation rate
+		# The rate should be recalculate based on the backdated purchase receipt
+		frappe.flags.print_debug_messages = False
+		item_code = make_item(
+			"_Test Item Sales Return with MA Case2",
+			{"is_stock_item": 1, "valuation_method": "Moving Average", "stock_uom": "Nos"},
+		).name
+
+		make_stock_entry(
+			item_code=item_code,
+			target="_Test Warehouse - _TC",
+			qty=5,
+			basic_rate=100.0,
+			posting_date=add_days(nowdate(), -5),
+		)
+
+		dn = create_delivery_note(
+			item_code=item_code,
+			warehouse="_Test Warehouse - _TC",
+			qty=5,
+			rate=500,
+			posting_date=add_days(nowdate(), -4),
+		)
+
+		returned_dn = create_delivery_note(
+			is_return=1,
+			item_code=item_code,
+			return_against=dn.name,
+			qty=-5,
+			rate=500,
+			company=dn.company,
+			warehouse="_Test Warehouse - _TC",
+			expense_account="Cost of Goods Sold - _TC",
+			cost_center="Main - _TC",
+			posting_date=add_days(nowdate(), -1),
+		)
+
+		self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 100.0)
+
+		# Make backdated purchase receipt
+		make_stock_entry(
+			item_code=item_code,
+			target="_Test Warehouse - _TC",
+			qty=5,
+			basic_rate=200.0,
+			posting_date=add_days(nowdate(), -3),
+		)
+
+		returned_dn.reload()
+		self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 200.0)
+
 
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index a942f58..b237f73 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -522,39 +522,25 @@
 		self.assertEqual(factor, 1.0)
 
 	def test_item_variant_by_manufacturer(self):
-		fields = [{"field_name": "description"}, {"field_name": "variant_based_on"}]
-		set_item_variant_settings(fields)
+		template = make_item(
+			"_Test Item Variant By Manufacturer", {"has_variants": 1, "variant_based_on": "Manufacturer"}
+		).name
 
-		if frappe.db.exists("Item", "_Test Variant Mfg"):
-			frappe.delete_doc("Item", "_Test Variant Mfg")
-		if frappe.db.exists("Item", "_Test Variant Mfg-1"):
-			frappe.delete_doc("Item", "_Test Variant Mfg-1")
-		if frappe.db.exists("Manufacturer", "MSG1"):
-			frappe.delete_doc("Manufacturer", "MSG1")
+		for manufacturer in ["DFSS", "DASA", "ASAAS"]:
+			if not frappe.db.exists("Manufacturer", manufacturer):
+				m_doc = frappe.new_doc("Manufacturer")
+				m_doc.short_name = manufacturer
+				m_doc.insert()
 
-		template = frappe.get_doc(
-			dict(
-				doctype="Item",
-				item_code="_Test Variant Mfg",
-				has_variant=1,
-				item_group="Products",
-				variant_based_on="Manufacturer",
-			)
-		).insert()
+		self.assertFalse(frappe.db.exists("Item Manufacturer", {"manufacturer": "DFSS"}))
+		variant = get_variant(template, manufacturer="DFSS", manufacturer_part_no="DFSS-123")
 
-		manufacturer = frappe.get_doc(dict(doctype="Manufacturer", short_name="MSG1")).insert()
+		item_manufacturer = frappe.db.exists(
+			"Item Manufacturer", {"manufacturer": "DFSS", "item_code": variant.name}
+		)
+		self.assertTrue(item_manufacturer)
 
-		variant = get_variant(template.name, manufacturer=manufacturer.name)
-		self.assertEqual(variant.item_code, "_Test Variant Mfg-1")
-		self.assertEqual(variant.description, "_Test Variant Mfg")
-		self.assertEqual(variant.manufacturer, "MSG1")
-		variant.insert()
-
-		variant = get_variant(template.name, manufacturer=manufacturer.name, manufacturer_part_no="007")
-		self.assertEqual(variant.item_code, "_Test Variant Mfg-2")
-		self.assertEqual(variant.description, "_Test Variant Mfg")
-		self.assertEqual(variant.manufacturer, "MSG1")
-		self.assertEqual(variant.manufacturer_part_no, "007")
+		frappe.delete_doc("Item Manufacturer", item_manufacturer)
 
 	def test_stock_exists_against_template_item(self):
 		stock_item = frappe.get_all("Stock Ledger Entry", fields=["item_code"], limit=1)
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 9673a70..d90b71a 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -199,9 +199,8 @@
 
 	get_item_data: function(frm, item, overwrite_warehouse=false) {
 		if (item && !item.item_code) { return; }
-		frm.call({
+		frappe.call({
 			method: "erpnext.stock.get_item_details.get_item_details",
-			child: item,
 			args: {
 				args: {
 					item_code: item.item_code,
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 7df74f8..ad9b34c 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -79,10 +79,10 @@
 		so_items = {}  # Format --> {'SO/00001': {'Item/001': 120, 'Item/002': 24}}
 		for d in self.get("items"):
 			if d.sales_order:
-				if not d.sales_order in so_items:
+				if d.sales_order not in so_items:
 					so_items[d.sales_order] = {d.item_code: flt(d.qty)}
 				else:
-					if not d.item_code in so_items[d.sales_order]:
+					if d.item_code not in so_items[d.sales_order]:
 						so_items[d.sales_order][d.item_code] = flt(d.qty)
 					else:
 						so_items[d.sales_order][d.item_code] += flt(d.qty)
@@ -169,7 +169,9 @@
 	def on_submit(self):
 		self.update_requested_qty_in_production_plan()
 		self.update_requested_qty()
-		if self.material_request_type == "Purchase":
+		if self.material_request_type == "Purchase" and frappe.db.exists(
+			"Budget", {"applicable_on_material_request": 1, "docstatus": 1}
+		):
 			self.validate_budget()
 
 	def before_save(self):
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 7cd171e..afd6ce8 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -283,6 +283,7 @@
 			});
 		}
 	},
+
 	uom: (frm, cdt, cdn) => {
 		let row = frappe.get_doc(cdt, cdn);
 		if (row.uom) {
@@ -291,13 +292,50 @@
 			});
 		}
 	},
+
 	qty: (frm, cdt, cdn) => {
 		let row = frappe.get_doc(cdt, cdn);
 		frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
 	},
+
 	conversion_factor: (frm, cdt, cdn) => {
 		let row = frappe.get_doc(cdt, cdn);
 		frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
+	},
+
+	pick_serial_and_batch(frm, cdt, cdn) {
+		let item = locals[cdt][cdn];
+		let path = "assets/erpnext/js/utils/serial_no_batch_selector.js";
+
+		frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
+			.then((r) => {
+				if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
+					item.has_serial_no = r.message.has_serial_no;
+					item.has_batch_no = r.message.has_batch_no;
+					item.type_of_transaction = item.qty > 0 ? "Outward":"Inward";
+
+					item.title = item.has_serial_no ?
+						__("Select Serial No") : __("Select Batch No");
+
+					if (item.has_serial_no && item.has_batch_no) {
+						item.title = __("Select Serial and Batch");
+					}
+
+					frappe.require(path, function() {
+						new erpnext.SerialBatchPackageSelector(
+							frm, item, (r) => {
+								if (r) {
+									let qty = Math.abs(r.total_qty);
+									frappe.model.set_value(item.doctype, item.name, {
+										"serial_and_batch_bundle": r.name,
+										"qty": qty
+									});
+								}
+							}
+						);
+					});
+				}
+			});
 	}
 });
 
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 545e45f..758448a 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -21,6 +21,7 @@
 )
 from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
 	get_auto_batch_nos,
+	get_picked_serial_nos,
 )
 from erpnext.stock.get_item_details import get_conversion_factor
 from erpnext.stock.serial_batch_bundle import SerialBatchCreation
@@ -167,6 +168,9 @@
 					"Serial and Batch Bundle", row.serial_and_batch_bundle
 				).set_serial_and_batch_values(self, row)
 
+	def on_trash(self):
+		self.remove_serial_and_batch_bundle()
+
 	def remove_serial_and_batch_bundle(self):
 		for row in self.locations:
 			if row.serial_and_batch_bundle:
@@ -723,13 +727,14 @@
 def get_available_item_locations_for_serialized_item(
 	item_code, from_warehouses, required_qty, company, total_picked_qty=0
 ):
+	picked_serial_nos = get_picked_serial_nos(item_code, from_warehouses)
+
 	sn = frappe.qb.DocType("Serial No")
 	query = (
 		frappe.qb.from_(sn)
 		.select(sn.name, sn.warehouse)
 		.where((sn.item_code == item_code) & (sn.company == company))
 		.orderby(sn.creation)
-		.limit(cint(required_qty + total_picked_qty))
 	)
 
 	if from_warehouses:
@@ -742,6 +747,9 @@
 	warehouse_serial_nos_map = frappe._dict()
 	picked_qty = required_qty
 	for serial_no, warehouse in serial_nos:
+		if serial_no in picked_serial_nos:
+			continue
+
 		if picked_qty <= 0:
 			break
 
@@ -786,7 +794,8 @@
 			{
 				"item_code": item_code,
 				"warehouse": from_warehouses,
-				"qty": required_qty + total_picked_qty,
+				"qty": required_qty,
+				"is_pick_list": True,
 			}
 		)
 	)
@@ -1050,7 +1059,7 @@
 @frappe.whitelist()
 def target_document_exists(pick_list_name, purpose):
 	if purpose == "Delivery":
-		return frappe.db.exists("Delivery Note", {"pick_list": pick_list_name})
+		return frappe.db.exists("Delivery Note", {"pick_list": pick_list_name, "docstatus": 1})
 
 	return stock_entry_exists(pick_list_name)
 
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 56c44bf..322b0b4 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -644,6 +644,122 @@
 		so.reload()
 		self.assertEqual(so.per_picked, 50)
 
+	def test_picklist_for_batch_item(self):
+		warehouse = "_Test Warehouse - _TC"
+		item = make_item(
+			properties={"is_stock_item": 1, "has_batch_no": 1, "batch_no_series": "PICKLT-.######"}
+		).name
+
+		# create batch
+		for batch_id in ["PICKLT-000001", "PICKLT-000002"]:
+			if not frappe.db.exists("Batch", batch_id):
+				frappe.get_doc(
+					{
+						"doctype": "Batch",
+						"batch_id": batch_id,
+						"item": item,
+					}
+				).insert()
+
+		make_stock_entry(
+			item=item,
+			to_warehouse=warehouse,
+			qty=50,
+			basic_rate=100,
+			batches=frappe._dict({"PICKLT-000001": 30, "PICKLT-000002": 20}),
+		)
+
+		so = make_sales_order(item_code=item, qty=25.0, rate=100)
+		pl = create_pick_list(so.name)
+		# 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)
+		# pick half the qty
+		for loc in pl.locations:
+			self.assertEqual(loc.qty, 10.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.assertTrue(d.batch_no in ["PICKLT-000001", "PICKLT-000002"])
+				if d.batch_no == "PICKLT-000001":
+					self.assertEqual(d.qty, 5.0 * -1)
+				elif d.batch_no == "PICKLT-000002":
+					self.assertEqual(d.qty, 5.0 * -1)
+
+		pl.save()
+		pl.submit()
+		pl.cancel()
+
+	def test_picklist_for_serial_item(self):
+		warehouse = "_Test Warehouse - _TC"
+		item = make_item(
+			properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SN-PICKLT-.######"}
+		).name
+
+		make_stock_entry(item=item, to_warehouse=warehouse, qty=50, basic_rate=100)
+
+		so = make_sales_order(item_code=item, qty=25.0, rate=100)
+		pl = create_pick_list(so.name)
+		picked_serial_nos = []
+		# 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=["serial_no"], filters={"parent": loc.serial_and_batch_bundle}
+			)
+
+			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)
+		# pick half the qty
+		for loc in pl.locations:
+			self.assertEqual(loc.qty, 10.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},
+			)
+
+			self.assertEqual(len(data), 10)
+			for d in data:
+				self.assertTrue(d.serial_no not in picked_serial_nos)
+
+		pl.save()
+		pl.submit()
+		pl.cancel()
+
 	def test_picklist_with_bundles(self):
 		warehouse = "_Test Warehouse - _TC"
 
@@ -732,7 +848,7 @@
 
 		dn.cancel()
 		pl.reload()
-		self.assertEqual(pl.status, "Completed")
+		self.assertEqual(pl.status, "Open")
 
 		pl.cancel()
 		pl.reload()
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 6c9d339..2cbccb0 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -88,6 +88,20 @@
 			}, __('Create'));
 		}
 
+		if (frm.doc.docstatus === 0) {
+			if (!frm.doc.is_return) {
+				frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
+					if (value) {
+						frm.doc.items.forEach((item) => {
+							frm.fields_dict.items.grid.update_docfield_property(
+								"rate", "read_only", (item.purchase_order && item.purchase_order_item)
+							);
+						});
+					}
+				});
+			}
+		}
+
 		frm.events.add_custom_buttons(frm);
 	},
 
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index c7ad660..a181022 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -289,7 +289,8 @@
    "no_copy": 1,
    "options": "Purchase Receipt",
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "search_index": 1
   },
   {
    "fieldname": "section_addresses",
@@ -1251,7 +1252,7 @@
  "idx": 261,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-11-28 13:14:15.243474",
+ "modified": "2023-12-18 17:26:41.279663",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 907bc67..10d9eaa 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -518,6 +518,7 @@
 							debit=0.0,
 							credit=discrepancy_caused_by_exchange_rate_difference,
 							remarks=remarks,
+							against_type="Supplier",
 							against_account=self.supplier,
 							debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
 							account_currency=account_currency,
@@ -531,6 +532,7 @@
 							debit=discrepancy_caused_by_exchange_rate_difference,
 							credit=0.0,
 							remarks=remarks,
+							against_type="Supplier",
 							against_account=self.supplier,
 							debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
 							account_currency=account_currency,
@@ -796,7 +798,7 @@
 			# Backward compatibility:
 			# and charges added via Landed Cost Voucher,
 			# post valuation related charges on "Stock Received But Not Billed"
-			against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0])
+			against_accounts = [d.account for d in gl_entries if flt(d.debit) > 0]
 			total_valuation_amount = sum(valuation_tax.values())
 			amount_including_divisional_loss = negative_expense_to_be_booked
 			stock_rbnb = (
@@ -828,16 +830,17 @@
 						)
 						amount_including_divisional_loss -= applicable_amount
 
-					self.add_gl_entry(
-						gl_entries=gl_entries,
-						account=account,
-						cost_center=tax.cost_center,
-						debit=0.0,
-						credit=applicable_amount,
-						remarks=self.remarks or _("Accounting Entry for Stock"),
-						against_account=against_account,
-						item=tax,
-					)
+					for against in against_accounts:
+						self.add_gl_entry(
+							gl_entries=gl_entries,
+							account=account,
+							cost_center=tax.cost_center,
+							debit=0.0,
+							credit=flt(applicable_amount) / len(against_accounts),
+							remarks=self.remarks or _("Accounting Entry for Stock"),
+							against_account=against,
+							item=tax,
+						)
 
 					i += 1
 
@@ -1208,7 +1211,6 @@
 		set_missing_values,
 	)
 
-	doclist.set_onload("ignore_price_list", True)
 	return doclist
 
 
@@ -1358,10 +1360,6 @@
 	return item_account_wise_cost
 
 
-def on_doctype_update():
-	frappe.db.add_index("Purchase Receipt", ["supplier", "is_return", "return_against"])
-
-
 @erpnext.allow_regional
 def update_regional_gl_entries(gl_list, doc):
 	return
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 146cbff..d274069 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -4,6 +4,7 @@
 import frappe
 from frappe.tests.utils import FrappeTestCase, change_settings
 from frappe.utils import add_days, cint, cstr, flt, nowtime, today
+from pypika import Order
 from pypika import functions as fn
 
 import erpnext
@@ -990,7 +991,7 @@
 		gl_entries = get_gl_entries("Purchase Receipt", pr.name)
 		sl_entries = get_sl_entries("Purchase Receipt", pr.name)
 
-		self.assertFalse(gl_entries)
+		self.assertEqual(len(gl_entries), 2)
 
 		expected_sle = {"Work In Progress - TCP1": -5, "Stores - TCP1": 5}
 
@@ -2235,13 +2236,13 @@
 
 
 def get_gl_entries(voucher_type, voucher_no):
-	return frappe.db.sql(
-		"""select account, debit, credit, cost_center, is_cancelled
-		from `tabGL Entry` where voucher_type=%s and voucher_no=%s
-		order by account desc""",
-		(voucher_type, voucher_no),
-		as_dict=1,
-	)
+	gle = frappe.qb.DocType("GL Entry")
+	return (
+		frappe.qb.from_(gle)
+		.select(gle.account, gle.debit, gle.credit, gle.cost_center, gle.is_cancelled)
+		.where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no))
+		.orderby(gle.account, gle.debit, order=Order.desc)
+	).run(as_dict=True)
 
 
 def get_taxes(**args):
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 7344d2a..9bd692a 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -359,7 +359,6 @@
    "oldfieldtype": "Currency",
    "options": "currency",
    "print_width": "100px",
-   "read_only_depends_on": "eval: (!parent.is_return && doc.purchase_order && doc.purchase_order_item)",
    "width": "100px"
   },
   {
@@ -1104,7 +1103,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-30 16:12:02.364608",
+ "modified": "2023-12-25 22:32:09.801965",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
index 3fc4e01..7ed6923 100644
--- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
@@ -170,7 +170,7 @@
 				pending_qty -= qty_to_allocate
 				rule["free_space"] -= stock_qty_to_allocate
 
-				if not pending_stock_qty > 0:
+				if pending_stock_qty <= 0:
 					break
 
 		# if pending qty after applying all rules, add row without warehouse
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 cda4445..9f01ee9 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
@@ -121,7 +121,7 @@
 			frappe.throw(__("Please attach CSV file"));
 		}
 
-		if (frm.doc.has_serial_no && !prompt_data.using_csv_file && !prompt_data.serial_nos) {
+		if (frm.doc.has_serial_no && !prompt_data.csv_file && !prompt_data.serial_nos) {
 			frappe.throw(__("Please enter serial nos"));
 		}
 	},
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
index d46b07a..7a58462 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
@@ -1,7 +1,7 @@
 {
  "actions": [],
  "autoname": "naming_series:",
- "creation": "2022-09-29 14:56:38.338267",
+ "creation": "2023-08-11 17:22:12.907518",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
@@ -250,7 +250,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-07-28 12:56:03.072224",
+ "modified": "2023-12-07 17:56:55.528563",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial and Batch Bundle",
@@ -270,6 +270,118 @@
    "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": "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 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 Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Delivery User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Delivery Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Manufacturing User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Manufacturing Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
   }
  ],
  "sort_field": "modified",
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 f0cff21..dd38e11 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
@@ -23,7 +23,11 @@
 )
 from frappe.utils.csvutils import build_csv_response
 
-from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
+from erpnext.stock.serial_batch_bundle import (
+	BatchNoValuation,
+	SerialNoValuation,
+	get_batches_from_bundle,
+)
 from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
 
 
@@ -81,6 +85,7 @@
 	# end: auto-generated types
 
 	def validate(self):
+		self.set_batch_no()
 		self.validate_serial_and_batch_no()
 		self.validate_duplicate_serial_and_batch_no()
 		self.validate_voucher_no()
@@ -95,6 +100,26 @@
 		self.set_incoming_rate()
 		self.calculate_qty_and_amount()
 
+	def set_batch_no(self):
+		if self.has_serial_no and self.has_batch_no:
+			serial_nos = [d.serial_no for d in self.entries if d.serial_no]
+			has_no_batch = any(not d.batch_no for d in self.entries)
+			if not has_no_batch:
+				return
+
+			serial_no_batch = frappe._dict(
+				frappe.get_all(
+					"Serial No",
+					filters={"name": ("in", serial_nos)},
+					fields=["name", "batch_no"],
+					as_list=True,
+				)
+			)
+
+			for row in self.entries:
+				if not row.batch_no:
+					row.batch_no = serial_no_batch.get(row.serial_no)
+
 	def validate_serial_nos_inventory(self):
 		if not (self.has_serial_no and self.type_of_transaction == "Outward"):
 			return
@@ -123,6 +148,11 @@
 				)
 
 	def validate_serial_nos_duplicate(self):
+		# Don't inward same serial number multiple times
+
+		if not self.warehouse:
+			return
+
 		if self.voucher_type in ["Stock Reconciliation", "Stock Entry"] and self.docstatus != 1:
 			return
 
@@ -146,7 +176,6 @@
 			kwargs["voucher_no"] = self.voucher_no
 
 		available_serial_nos = get_available_serial_nos(kwargs)
-
 		for data in available_serial_nos:
 			if data.serial_no in serial_nos:
 				self.throw_error_message(
@@ -252,6 +281,7 @@
 				"serial_nos": [row.serial_no for row in self.entries if row.serial_no],
 				"batch_nos": {row.batch_no: row for row in self.entries if row.batch_no},
 				"voucher_type": self.voucher_type,
+				"voucher_detail_no": self.voucher_detail_no,
 			}
 		)
 
@@ -326,6 +356,19 @@
 		):
 			values_to_set["posting_time"] = parent.posting_time
 
+		if parent.doctype in [
+			"Delivery Note",
+			"Purchase Receipt",
+			"Purchase Invoice",
+			"Sales Invoice",
+		] and parent.get("is_return"):
+			return_ref_field = frappe.scrub(parent.doctype) + "_item"
+			if parent.doctype == "Delivery Note":
+				return_ref_field = "dn_detail"
+
+			if row.get(return_ref_field):
+				values_to_set["returned_against"] = row.get(return_ref_field)
+
 		if values_to_set:
 			self.db_set(values_to_set)
 
@@ -437,7 +480,7 @@
 			qty_field = "qty"
 
 		precision = row.precision
-		if self.voucher_type in ["Subcontracting Receipt"]:
+		if row.get("doctype") in ["Subcontracting Receipt Supplied Item"]:
 			qty_field = "consumed_qty"
 
 		if abs(abs(flt(self.total_qty, precision)) - abs(flt(row.get(qty_field), precision))) > 0.01:
@@ -508,8 +551,23 @@
 		batch_nos = []
 
 		serial_batches = {}
-
 		for row in self.entries:
+			if self.has_serial_no and not row.serial_no:
+				frappe.throw(
+					_("At row {0}: Serial No is mandatory for Item {1}").format(
+						bold(row.idx), bold(self.item_code)
+					),
+					title=_("Serial No is mandatory"),
+				)
+
+			if self.has_batch_no and not row.batch_no:
+				frappe.throw(
+					_("At row {0}: Batch No is mandatory for Item {1}").format(
+						bold(row.idx), bold(self.item_code)
+					),
+					title=_("Batch No is mandatory"),
+				)
+
 			if row.serial_no:
 				serial_nos.append(row.serial_no)
 
@@ -573,6 +631,67 @@
 				f"Batch Nos {bold(incorrect_batch_nos)} does not belong to Item {bold(self.item_code)}"
 			)
 
+	def validate_serial_and_batch_no_for_returned(self):
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+		if not self.returned_against:
+			return
+
+		if self.voucher_type not in [
+			"Purchase Receipt",
+			"Purchase Invoice",
+			"Sales Invoice",
+			"Delivery Note",
+		]:
+			return
+
+		data = self.get_orignal_document_data()
+		if not data:
+			return
+
+		serial_nos, batches = [], []
+		current_serial_nos = [d.serial_no for d in self.entries if d.serial_no]
+		current_batches = [d.batch_no for d in self.entries if d.batch_no]
+
+		for d in data:
+			if self.has_serial_no:
+				if d.serial_and_batch_bundle:
+					serial_nos = get_serial_nos_from_bundle(d.serial_and_batch_bundle)
+				else:
+					serial_nos = get_serial_nos(d.serial_no)
+
+			elif self.has_batch_no:
+				if d.serial_and_batch_bundle:
+					batches = get_batches_from_bundle(d.serial_and_batch_bundle)
+				else:
+					batches = frappe._dict({d.batch_no: d.stock_qty})
+
+				if batches:
+					batches = [d for d in batches if batches[d] > 0]
+
+			if serial_nos:
+				if not set(current_serial_nos).issubset(set(serial_nos)):
+					self.throw_error_message(
+						f"Serial Nos {bold(', '.join(serial_nos))} are not part of the original document."
+					)
+
+			if batches:
+				if not set(current_batches).issubset(set(batches)):
+					self.throw_error_message(
+						f"Batch Nos {bold(', '.join(batches))} are not part of the original document."
+					)
+
+	def get_orignal_document_data(self):
+		fields = ["serial_and_batch_bundle", "stock_qty"]
+		if self.has_serial_no:
+			fields.append("serial_no")
+
+		elif self.has_batch_no:
+			fields.append("batch_no")
+
+		child_doc = self.voucher_type + " Item"
+		return frappe.get_all(child_doc, fields=fields, filters={"name": self.returned_against})
+
 	def validate_duplicate_serial_and_batch_no(self):
 		serial_nos = []
 		batch_nos = []
@@ -671,9 +790,29 @@
 		for batch in batches:
 			frappe.db.set_value("Batch", batch.name, {"reference_name": None, "reference_doctype": None})
 
+	def before_submit(self):
+		self.validate_serial_and_batch_no_for_returned()
+		self.set_purchase_document_no()
+
 	def on_submit(self):
 		self.validate_serial_nos_inventory()
 
+	def set_purchase_document_no(self):
+		if not self.has_serial_no:
+			return
+
+		if self.total_qty > 0:
+			serial_nos = [d.serial_no for d in self.entries if d.serial_no]
+			sn_table = frappe.qb.DocType("Serial No")
+			(
+				frappe.qb.update(sn_table)
+				.set(
+					sn_table.purchase_document_no,
+					self.voucher_no if not sn_table.purchase_document_no else self.voucher_no,
+				)
+				.where(sn_table.name.isin(serial_nos))
+			).run()
+
 	def validate_serial_and_batch_inventory(self):
 		self.check_future_entries_exists()
 		self.validate_batch_inventory()
@@ -692,6 +831,7 @@
 					"item_code": self.item_code,
 					"warehouse": self.warehouse,
 					"batch_no": batches,
+					"consider_negative_batches": True,
 				}
 			)
 		)
@@ -702,6 +842,9 @@
 		available_batches = get_available_batches_qty(available_batches)
 		for batch_no in batches:
 			if batch_no not in available_batches or available_batches[batch_no] < 0:
+				if flt(available_batches.get(batch_no)) < 0:
+					self.validate_negative_batch(batch_no, available_batches[batch_no])
+
 				self.throw_error_message(
 					f"Batch {bold(batch_no)} is not available in the selected warehouse {self.warehouse}"
 				)
@@ -793,6 +936,9 @@
 		if index == 0:
 			has_serial_no = row[0] == "Serial No"
 			has_batch_no = row[0] == "Batch No"
+			if not has_batch_no and len(row) > 1:
+				has_batch_no = row[1] == "Batch No"
+
 			continue
 
 		if not row[0]:
@@ -809,6 +955,13 @@
 					}
 				)
 
+				batch_nos.append(
+					{
+						"batch_no": row[1],
+						"qty": row[2],
+					}
+				)
+
 			serial_nos.append(_dict)
 		elif has_batch_no:
 			batch_nos.append(
@@ -844,6 +997,9 @@
 	serial_nos_details = []
 	user = frappe.session.user
 	for serial_no in serial_nos:
+		if frappe.db.exists("Serial No", serial_no):
+			continue
+
 		serial_nos_details.append(
 			(
 				serial_no,
@@ -874,7 +1030,7 @@
 
 	frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
 
-	frappe.msgprint(_("Serial Nos are created successfully"))
+	frappe.msgprint(_("Serial Nos are created successfully"), alert=True)
 
 
 def make_batch_nos(item_code, batch_nos):
@@ -885,6 +1041,9 @@
 	batch_nos_details = []
 	user = frappe.session.user
 	for batch_no in batch_nos:
+		if frappe.db.exists("Batch", batch_no):
+			continue
+
 		batch_nos_details.append(
 			(batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description)
 		)
@@ -903,7 +1062,7 @@
 
 	frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details))
 
-	frappe.msgprint(_("Batch Nos are created successfully"))
+	frappe.msgprint(_("Batch Nos are created successfully"), alert=True)
 
 
 def parse_serial_nos(data):
@@ -1026,7 +1185,7 @@
 		doc.append(
 			"entries",
 			{
-				"qty": (row.qty or 1.0) * (1 if type_of_transaction == "Inward" else -1),
+				"qty": (flt(row.qty) or 1.0) * (1 if type_of_transaction == "Inward" else -1),
 				"warehouse": warehouse,
 				"batch_no": row.batch_no,
 				"serial_no": row.serial_no,
@@ -1054,7 +1213,7 @@
 		doc.append(
 			"entries",
 			{
-				"qty": (d.get("qty") or 1.0) * (1 if doc.type_of_transaction == "Inward" else -1),
+				"qty": (flt(d.get("qty")) or 1.0) * (1 if doc.type_of_transaction == "Inward" else -1),
 				"warehouse": warehouse or d.get("warehouse"),
 				"batch_no": d.get("batch_no"),
 				"serial_no": d.get("serial_no"),
@@ -1452,13 +1611,21 @@
 	stock_ledgers_batches = get_stock_ledgers_batches(kwargs)
 	pos_invoice_batches = get_reserved_batches_for_pos(kwargs)
 	sre_reserved_batches = get_reserved_batches_for_sre(kwargs)
+	picked_batches = frappe._dict()
+	if kwargs.get("is_pick_list"):
+		picked_batches = get_picked_batches(kwargs)
 
-	if stock_ledgers_batches or pos_invoice_batches or sre_reserved_batches:
+	if stock_ledgers_batches or pos_invoice_batches or sre_reserved_batches or picked_batches:
 		update_available_batches(
-			available_batches, stock_ledgers_batches, pos_invoice_batches, sre_reserved_batches
+			available_batches,
+			stock_ledgers_batches,
+			pos_invoice_batches,
+			sre_reserved_batches,
+			picked_batches,
 		)
 
-	available_batches = list(filter(lambda x: x.qty > 0, available_batches))
+	if not kwargs.consider_negative_batches:
+		available_batches = list(filter(lambda x: x.qty > 0, available_batches))
 
 	if not qty:
 		return available_batches
@@ -1611,6 +1778,102 @@
 	return group_by_voucher
 
 
+def get_picked_batches(kwargs) -> dict[str, dict]:
+	picked_batches = frappe._dict()
+
+	table = frappe.qb.DocType("Serial and Batch Bundle")
+	child_table = frappe.qb.DocType("Serial and Batch Entry")
+	pick_list_table = frappe.qb.DocType("Pick List")
+
+	query = (
+		frappe.qb.from_(table)
+		.inner_join(child_table)
+		.on(table.name == child_table.parent)
+		.inner_join(pick_list_table)
+		.on(table.voucher_no == pick_list_table.name)
+		.select(
+			child_table.batch_no,
+			child_table.warehouse,
+			Sum(child_table.qty).as_("qty"),
+		)
+		.where(
+			(table.docstatus != 2)
+			& (pick_list_table.status != "Completed")
+			& (table.type_of_transaction == "Outward")
+			& (table.is_cancelled == 0)
+			& (table.voucher_type == "Pick List")
+			& (table.voucher_no.isnotnull())
+		)
+	)
+
+	if kwargs.get("item_code"):
+		query = query.where(table.item_code == kwargs.get("item_code"))
+
+	if kwargs.get("warehouse"):
+		if isinstance(kwargs.warehouse, list):
+			query = query.where(table.warehouse.isin(kwargs.warehouse))
+		else:
+			query = query.where(table.warehouse == kwargs.get("warehouse"))
+
+	data = query.run(as_dict=True)
+	for row in data:
+		if not row.qty:
+			continue
+
+		key = (row.batch_no, row.warehouse)
+		if key not in picked_batches:
+			picked_batches[key] = frappe._dict(
+				{
+					"qty": row.qty,
+					"warehouse": row.warehouse,
+				}
+			)
+		else:
+			picked_batches[key].qty += row.qty
+
+	return picked_batches
+
+
+def get_picked_serial_nos(item_code, warehouse=None) -> list[str]:
+	table = frappe.qb.DocType("Serial and Batch Bundle")
+	child_table = frappe.qb.DocType("Serial and Batch Entry")
+	pick_list_table = frappe.qb.DocType("Pick List")
+
+	query = (
+		frappe.qb.from_(table)
+		.inner_join(child_table)
+		.on(table.name == child_table.parent)
+		.inner_join(pick_list_table)
+		.on(table.voucher_no == pick_list_table.name)
+		.select(
+			child_table.serial_no,
+		)
+		.where(
+			(table.docstatus != 2)
+			& (pick_list_table.status != "Completed")
+			& (table.type_of_transaction == "Outward")
+			& (table.is_cancelled == 0)
+			& (table.voucher_type == "Pick List")
+			& (table.voucher_no.isnotnull())
+		)
+	)
+
+	if item_code:
+		query = query.where(table.item_code == item_code)
+
+	if warehouse:
+		if isinstance(warehouse, list):
+			query = query.where(table.warehouse.isin(warehouse))
+		else:
+			query = query.where(table.warehouse == warehouse)
+
+	data = query.run(as_dict=True)
+	if not data:
+		return []
+
+	return [row.serial_no for row in data if row.serial_no]
+
+
 def get_ledgers_from_serial_batch_bundle(**kwargs) -> List[frappe._dict]:
 	bundle_table = frappe.qb.DocType("Serial and Batch Bundle")
 	serial_batch_table = frappe.qb.DocType("Serial and Batch Entry")
@@ -1744,3 +2007,8 @@
 			batches[key].qty += d.qty
 
 	return batches
+
+
+@frappe.whitelist()
+def get_batch_no_from_serial_no(serial_no):
+	return frappe.get_cached_value("Serial No", serial_no, "batch_no")
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 0e01b20..d74d657 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
@@ -368,6 +368,58 @@
 		# Batch does not belong to serial no
 		self.assertRaises(frappe.exceptions.ValidationError, doc.save)
 
+	def test_auto_delete_draft_serial_and_batch_bundle(self):
+		serial_and_batch_code = "New Serial No Auto Delete 1"
+		make_item(
+			serial_and_batch_code,
+			{
+				"has_serial_no": 1,
+				"serial_no_series": "TEST-SER-VALL-.#####",
+				"is_stock_item": 1,
+			},
+		)
+
+		ste = make_stock_entry(
+			item_code=serial_and_batch_code,
+			target="_Test Warehouse - _TC",
+			qty=1,
+			rate=500,
+			do_not_submit=True,
+		)
+
+		serial_no = "SN-TEST-AUTO-DEL"
+		if not frappe.db.exists("Serial No", serial_no):
+			frappe.get_doc(
+				{
+					"doctype": "Serial No",
+					"serial_no": serial_no,
+					"item_code": serial_and_batch_code,
+					"company": "_Test Company",
+				}
+			).insert(ignore_permissions=True)
+
+		bundle_doc = make_serial_batch_bundle(
+			{
+				"item_code": serial_and_batch_code,
+				"warehouse": "_Test Warehouse - _TC",
+				"voucher_type": "Stock Entry",
+				"posting_date": ste.posting_date,
+				"posting_time": ste.posting_time,
+				"qty": 1,
+				"serial_nos": [serial_no],
+				"type_of_transaction": "Inward",
+				"do_not_submit": True,
+			}
+		)
+
+		bundle_doc.reload()
+		ste.items[0].serial_and_batch_bundle = bundle_doc.name
+		ste.save()
+		ste.reload()
+
+		ste.delete()
+		self.assertFalse(frappe.db.exists("Serial and Batch Bundle", bundle_doc.name))
+
 
 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 09565cb..5de2c2e 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
@@ -27,7 +27,6 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Serial No",
-   "mandatory_depends_on": "eval:parent.has_serial_no == 1",
    "options": "Serial No",
    "search_index": 1
   },
@@ -38,7 +37,6 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Batch No",
-   "mandatory_depends_on": "eval:parent.has_batch_no == 1",
    "options": "Batch",
    "search_index": 1
   },
@@ -122,7 +120,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-07-03 15:29:50.199075",
+ "modified": "2023-12-10 19:47:48.227772",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial and Batch Entry",
diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json
index b4ece00..2d7fcac 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.json
+++ b/erpnext/stock/doctype/serial_no/serial_no.json
@@ -27,8 +27,6 @@
   "column_break_24",
   "location",
   "employee",
-  "delivery_details",
-  "delivery_document_type",
   "warranty_amc_details",
   "column_break6",
   "warranty_expiry_date",
@@ -39,7 +37,8 @@
   "more_info",
   "company",
   "column_break_2cmm",
-  "work_order"
+  "work_order",
+  "purchase_document_no"
  ],
  "fields": [
   {
@@ -154,20 +153,6 @@
    "read_only": 1
   },
   {
-   "fieldname": "delivery_details",
-   "fieldtype": "Section Break",
-   "label": "Delivery Details",
-   "oldfieldtype": "Column Break"
-  },
-  {
-   "fieldname": "delivery_document_type",
-   "fieldtype": "Link",
-   "label": "Delivery Document Type",
-   "no_copy": 1,
-   "options": "DocType",
-   "read_only": 1
-  },
-  {
    "fieldname": "warranty_amc_details",
    "fieldtype": "Section Break",
    "label": "Warranty / AMC Details"
@@ -275,12 +260,19 @@
   {
    "fieldname": "column_break_2cmm",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "purchase_document_no",
+   "fieldtype": "Data",
+   "label": "Creation Document No",
+   "no_copy": 1,
+   "read_only": 1
   }
  ],
  "icon": "fa fa-barcode",
  "idx": 1,
  "links": [],
- "modified": "2023-11-28 15:37:59.489945",
+ "modified": "2023-12-17 10:52:55.767839",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial No",
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index d562560..122664c 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -41,7 +41,6 @@
 		batch_no: DF.Link | None
 		brand: DF.Link | None
 		company: DF.Link
-		delivery_document_type: DF.Link | None
 		description: DF.Text | None
 		employee: DF.Link | None
 		item_code: DF.Link
@@ -51,6 +50,7 @@
 		maintenance_status: DF.Literal[
 			"", "Under Warranty", "Out of Warranty", "Under AMC", "Out of AMC"
 		]
+		purchase_document_no: DF.Data | None
 		purchase_rate: DF.Float
 		serial_no: DF.Data
 		status: DF.Literal["", "Active", "Inactive", "Delivered", "Expired"]
@@ -231,26 +231,6 @@
 	return sorted([d.get("name") for d in serial_numbers])
 
 
-def get_delivered_serial_nos(serial_nos):
-	"""
-	Returns serial numbers that delivered from the list of serial numbers
-	"""
-	from frappe.query_builder.functions import Coalesce
-
-	SerialNo = frappe.qb.DocType("Serial No")
-	serial_nos = get_serial_nos(serial_nos)
-	query = (
-		frappe.qb.select(SerialNo.name)
-		.from_(SerialNo)
-		.where((SerialNo.name.isin(serial_nos)) & (Coalesce(SerialNo.delivery_document_type, "") != ""))
-	)
-
-	result = query.run()
-	if result and len(result) > 0:
-		delivered_serial_nos = [row[0] for row in result]
-		return delivered_serial_nos
-
-
 @frappe.whitelist()
 def get_pos_reserved_serial_nos(filters):
 	if isinstance(filters, str):
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 7334b35..8da3e8f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -512,7 +512,12 @@
 				},
 				callback: function(r) {
 					if (!r.exc) {
-						["actual_qty", "basic_rate"].forEach((field) => {
+						let fields = ["actual_qty", "basic_rate"];
+						if (frm.doc.purpose == "Material Receipt") {
+							fields = ["actual_qty"];
+						}
+
+						fields.forEach((field) => {
 							frappe.model.set_value(cdt, cdn, field, (r.message[field] || 0.0));
 						});
 						frm.events.calculate_basic_amount(frm, child);
@@ -781,10 +786,9 @@
 						});
 						refresh_field("items");
 
-						let no_batch_serial_number_value = !d.serial_no;
-						if (d.has_batch_no && !d.has_serial_no) {
-							// check only batch_no for batched item
-							no_batch_serial_number_value = !d.batch_no;
+						let no_batch_serial_number_value = false;
+						if (d.has_serial_no || d.has_batch_no) {
+							no_batch_serial_number_value = true;
 						}
 
 						if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) {
@@ -941,6 +945,7 @@
 	}
 
 	scan_barcode() {
+		frappe.flags.dialog_set = false;
 		const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
 		barcode_scanner.process_scan();
 	}
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 8158b99..6521394 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -227,7 +227,7 @@
 		self.calculate_rate_and_amount()
 		self.validate_putaway_capacity()
 
-		if not self.get("purpose") == "Manufacture":
+		if self.get("purpose") != "Manufacture":
 			# ignore scrap item wh difference and empty source/target wh
 			# in Manufacture Entry
 			self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
@@ -883,6 +883,7 @@
 				"company": self.company,
 				"allow_zero_valuation": item.allow_zero_valuation_rate,
 				"serial_and_batch_bundle": item.serial_and_batch_bundle,
+				"voucher_detail_no": item.name,
 			}
 		)
 
@@ -1462,7 +1463,9 @@
 						self.get_gl_dict(
 							{
 								"account": account,
+								"against_type": "Account",
 								"against": d.expense_account,
+								"against_link": d.expense_account,
 								"cost_center": d.cost_center,
 								"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
 								"credit_in_account_currency": flt(amount["amount"]),
@@ -1476,7 +1479,9 @@
 						self.get_gl_dict(
 							{
 								"account": d.expense_account,
+								"against_type": "Account",
 								"against": account,
+								"against_link": account,
 								"cost_center": d.cost_center,
 								"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
 								"credit": -1
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index b640983..d400312 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -504,7 +504,14 @@
 		self.check_gl_entries(
 			"Stock Entry",
 			repack.name,
-			sorted([[stock_in_hand_account, 1200, 0.0], ["Cost of Goods Sold - TCP1", 0.0, 1200.0]]),
+			sorted(
+				[
+					["Cost of Goods Sold - TCP1", 0.0, 1200.0],
+					["Stock Adjustment - TCP1", 0.0, 1200.0],
+					["Stock Adjustment - TCP1", 1200.0, 0.0],
+					[stock_in_hand_account, 1200.0, 0.0],
+				]
+			),
 		)
 
 	def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle):
@@ -936,6 +943,42 @@
 		stock_entry.insert()
 		self.assertTrue("_Test Variant Item-S" in [d.item_code for d in stock_entry.items])
 
+	def test_nagative_stock_for_batch(self):
+		item = make_item(
+			"_Test Batch Negative Item",
+			{
+				"has_batch_no": 1,
+				"create_new_batch": 1,
+				"batch_number_series": "B-BATCH-.##",
+				"is_stock_item": 1,
+			},
+		)
+
+		make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=50, basic_rate=100)
+
+		ste = frappe.new_doc("Stock Entry")
+		ste.purpose = "Material Issue"
+		ste.company = "_Test Company"
+		for qty in [50, 20, 30]:
+			ste.append(
+				"items",
+				{
+					"item_code": item.name,
+					"s_warehouse": "_Test Warehouse - _TC",
+					"qty": qty,
+					"uom": item.stock_uom,
+					"stock_uom": item.stock_uom,
+					"conversion_factor": 1,
+					"transfer_qty": qty,
+				},
+			)
+
+		ste.set_stock_entry_type()
+		ste.insert()
+		make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=50, basic_rate=100)
+
+		self.assertRaises(frappe.ValidationError, ste.submit)
+
 	def test_same_serial_nos_in_repack_or_manufacture_entries(self):
 		s1 = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
 		serial_nos = get_serial_nos_from_bundle(s1.get("items")[0].serial_and_batch_bundle)
@@ -1697,6 +1740,45 @@
 		self.assertFalse(doc.is_enqueue_action())
 		frappe.flags.in_test = True
 
+	def test_negative_batch(self):
+		item_code = "Test Negative Batch Item - 001"
+		make_item(
+			item_code,
+			{"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "Test-BCH-NNS.#####"},
+		)
+
+		se1 = make_stock_entry(
+			item_code=item_code,
+			purpose="Material Receipt",
+			qty=100,
+			target="_Test Warehouse - _TC",
+		)
+
+		se1.reload()
+
+		batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle)
+
+		se2 = make_stock_entry(
+			item_code=item_code,
+			purpose="Material Issue",
+			batch_no=batch_no,
+			qty=10,
+			source="_Test Warehouse - _TC",
+		)
+
+		se2.reload()
+
+		se3 = make_stock_entry(
+			item_code=item_code,
+			purpose="Material Receipt",
+			qty=100,
+			target="_Test Warehouse - _TC",
+		)
+
+		se3.reload()
+
+		self.assertRaises(frappe.ValidationError, se1.cancel)
+
 
 def make_serialized_item(**args):
 	args = frappe._dict(args)
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 e62f0b2..6e7af68 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -181,6 +181,9 @@
 			frappe.throw(_("Actual Qty is mandatory"))
 
 	def validate_serial_batch_no_bundle(self):
+		if self.is_cancelled == 1:
+			return
+
 		item_detail = frappe.get_cached_value(
 			"Item",
 			self.item_code,
@@ -214,7 +217,9 @@
 			if not self.serial_and_batch_bundle:
 				self.throw_error_message(f"Serial No / Batch No are mandatory for Item {self.item_code}")
 
-		if self.serial_and_batch_bundle and not (item_detail.has_serial_no or item_detail.has_batch_no):
+		if (
+			self.serial_and_batch_bundle and not item_detail.has_serial_no and not item_detail.has_batch_no
+		):
 			self.throw_error_message(f"Serial No and Batch No are not allowed for Item {self.item_code}")
 
 	def throw_error_message(self, message, exception=frappe.ValidationError):
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 f7c6ffe..d8a3f2e 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
@@ -485,9 +485,9 @@
 		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]
-		expected_incoming_rates = expected_abs_svd = sorted([75.0, 125.0, 75.0, 125.0])
+		expected_incoming_rates = expected_abs_svd = [75.0, 125.0, 75.0, 125.0]
 
-		self.assertEqual(expected_abs_svd, sorted(svd_list), "Incorrect 'Stock Value Difference' values")
+		self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values")
 		for dn, incoming_rate in zip(dns, expected_incoming_rates):
 			self.assertTrue(
 				dn.items[0].incoming_rate in expected_abs_svd,
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index b3998b7..8e9dcb0 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -209,7 +209,7 @@
 
 	set_amount_quantity: function(doc, cdt, cdn) {
 		var d = frappe.model.get_doc(cdt, cdn);
-		if (d.qty & d.valuation_rate) {
+		if (d.qty && d.valuation_rate) {
 			frappe.model.set_value(cdt, cdn, "amount", flt(d.qty) * flt(d.valuation_rate));
 			frappe.model.set_value(cdt, cdn, "quantity_difference", flt(d.qty) - flt(d.current_qty));
 			frappe.model.set_value(cdt, cdn, "amount_difference", flt(d.amount) - flt(d.current_amount));
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index e8d652e..6819968 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -171,7 +171,7 @@
 						},
 					)
 
-			if item_details.has_batch_no:
+			elif item_details.has_batch_no:
 				batch_nos_details = get_available_batches(
 					frappe._dict(
 						{
@@ -228,6 +228,9 @@
 
 	def set_new_serial_and_batch_bundle(self):
 		for item in self.items:
+			if not item.qty:
+				continue
+
 			if item.current_serial_and_batch_bundle and not item.serial_and_batch_bundle:
 				current_doc = frappe.get_doc("Serial and Batch Bundle", item.current_serial_and_batch_bundle)
 
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 1ec99bf..70e9fb2 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -865,6 +865,66 @@
 		sr1.load_from_db()
 		self.assertEqual(sr1.difference_amount, 10000)
 
+	def test_make_stock_zero_for_serial_batch_item(self):
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+		serial_item = self.make_item(
+			properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "DJJ.####"}
+		).name
+		batch_item = self.make_item(
+			properties={
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"batch_number_series": "BDJJ.####",
+				"create_new_batch": 1,
+			}
+		).name
+
+		serial_batch_item = self.make_item(
+			properties={
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"batch_number_series": "ADJJ.####",
+				"create_new_batch": 1,
+				"has_serial_no": 1,
+				"serial_no_series": "SN-ADJJ.####",
+			}
+		).name
+
+		warehouse = "_Test Warehouse - _TC"
+
+		for item_code in [serial_item, batch_item, serial_batch_item]:
+			make_stock_entry(
+				item_code=item_code,
+				target=warehouse,
+				qty=10,
+				basic_rate=100,
+			)
+
+			_reco = create_stock_reconciliation(
+				item_code=item_code,
+				warehouse=warehouse,
+				qty=0.0,
+			)
+
+			serial_batch_bundle = frappe.get_all(
+				"Stock Ledger Entry",
+				{"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0, "voucher_no": _reco.name},
+				"serial_and_batch_bundle",
+			)
+
+			self.assertEqual(len(serial_batch_bundle), 1)
+
+			_reco.cancel()
+
+			serial_batch_bundle = frappe.get_all(
+				"Stock Ledger Entry",
+				{"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0, "voucher_no": _reco.name},
+				"serial_and_batch_bundle",
+			)
+
+			self.assertEqual(len(serial_batch_bundle), 0)
+
 
 def create_batch_item_with_batch(item_name, batch_id):
 	batch_item_doc = create_item(item_name, is_stock_item=1)
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 85550c2..7e03ac3 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
@@ -9,7 +9,7 @@
 from frappe.query_builder.functions import Sum
 from frappe.utils import cint, flt
 
-from erpnext.stock.utils import get_or_make_bin
+from erpnext.stock.utils import get_or_make_bin, get_stock_balance
 
 
 class StockReservationEntry(Document):
@@ -151,7 +151,7 @@
 		"""Validates `Reserved Qty` when `Reservation Based On` is `Qty`."""
 
 		if self.reservation_based_on == "Qty":
-			self.validate_with_max_reserved_qty(self.reserved_qty)
+			self.validate_with_allowed_qty(self.reserved_qty)
 
 	def auto_reserve_serial_and_batch(self, based_on: str = None) -> None:
 		"""Auto pick Serial and Batch Nos to reserve when `Reservation Based On` is `Serial and Batch`."""
@@ -324,7 +324,7 @@
 				frappe.throw(msg)
 
 			# Should be called after validating Serial and Batch Nos.
-			self.validate_with_max_reserved_qty(qty_to_be_reserved)
+			self.validate_with_allowed_qty(qty_to_be_reserved)
 			self.db_set("reserved_qty", qty_to_be_reserved)
 
 	def update_reserved_qty_in_voucher(
@@ -429,7 +429,7 @@
 			msg = _("Stock Reservation Entry cannot be updated as it has been delivered.")
 			frappe.throw(msg)
 
-	def validate_with_max_reserved_qty(self, qty_to_be_reserved: float) -> None:
+	def validate_with_allowed_qty(self, qty_to_be_reserved: float) -> None:
 		"""Validates `Reserved Qty` with `Max Reserved Qty`."""
 
 		self.db_set(
@@ -448,12 +448,12 @@
 			)
 			voucher_delivered_qty = flt(delivered_qty) * flt(conversion_factor)
 
-		max_reserved_qty = min(
+		allowed_qty = min(
 			self.available_qty, (self.voucher_qty - voucher_delivered_qty - total_reserved_qty)
 		)
 
-		if max_reserved_qty <= 0 and self.voucher_type == "Sales Order":
-			msg = _("Item {0} is already delivered for Sales Order {1}.").format(
+		if self.get("_action") != "submit" and self.voucher_type == "Sales Order" and allowed_qty <= 0:
+			msg = _("Item {0} is already reserved/delivered against Sales Order {1}.").format(
 				frappe.bold(self.item_code), frappe.bold(self.voucher_no)
 			)
 
@@ -463,19 +463,33 @@
 			else:
 				frappe.throw(msg)
 
-		if qty_to_be_reserved > max_reserved_qty:
+		if qty_to_be_reserved > allowed_qty:
+			actual_qty = get_stock_balance(self.item_code, self.warehouse)
 			msg = """
-				Cannot reserve more than Max Reserved Qty {0} {1}.<br /><br />
-				The <b>Max Reserved Qty</b> is calculated as follows:<br />
+				Cannot reserve more than Allowed Qty {0} {1} for Item {2} against {3} {4}.<br /><br />
+				The <b>Allowed Qty</b> is calculated as follows:<br />
 				<ul>
-					<li><b>Available Qty To Reserve</b> = (Actual Stock Qty - Reserved Stock Qty)</li>
-					<li><b>Voucher Qty</b> = Voucher Item Qty</li>
-					<li><b>Delivered Qty</b> = Qty delivered against the Voucher Item</li>
-					<li><b>Total Reserved Qty</b> = Qty reserved against the Voucher Item</li>
-					<li><b>Max Reserved Qty</b> = Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))</li>
+					<li>Actual Qty [Available Qty at Warehouse] = {5}</li>
+					<li>Reserved Stock [Ignore current SRE] = {6}</li>
+					<li>Available Qty To Reserve [Actual Qty - Reserved Stock] = {7}</li>
+					<li>Voucher Qty [Voucher Item Qty] = {8}</li>
+					<li>Delivered Qty [Qty delivered against the Voucher Item] = {9}</li>
+					<li>Total Reserved Qty [Qty reserved against the Voucher Item] = {10}</li>
+					<li>Allowed Qty [Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))] = {11}</li>
 				</ul>
 			""".format(
-				frappe.bold(max_reserved_qty), self.stock_uom
+				frappe.bold(allowed_qty),
+				self.stock_uom,
+				frappe.bold(self.item_code),
+				self.voucher_type,
+				frappe.bold(self.voucher_no),
+				actual_qty,
+				actual_qty - self.available_qty,
+				self.available_qty,
+				self.voucher_qty,
+				voucher_delivered_qty,
+				total_reserved_qty,
+				allowed_qty,
 			)
 			frappe.throw(msg)
 
@@ -509,7 +523,6 @@
 	"""Returns `Available Qty to Reserve (Actual Qty - Reserved Qty)` for Item, Warehouse and Batch combination."""
 
 	from erpnext.stock.doctype.batch.batch import get_batch_qty
-	from erpnext.stock.utils import get_stock_balance
 
 	if batch_no:
 		return get_batch_qty(
@@ -931,7 +944,7 @@
 			continue
 
 		# Stock should be reserved from the Pick List if has Picked Qty.
-		if not from_voucher_type == "Pick List" and flt(item.picked_qty) > 0:
+		if from_voucher_type != "Pick List" and flt(item.picked_qty) > 0:
 			frappe.throw(
 				_("Row #{0}: Item {1} has been picked, please reserve stock from the Pick List.").format(
 					item.idx, frappe.bold(item.item_code)
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index dfeb1ee..e746595 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -358,7 +358,6 @@
 			"net_amount": 0.0,
 			"discount_percentage": 0.0,
 			"discount_amount": flt(args.discount_amount) or 0.0,
-			"supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults),
 			"update_stock": args.get("update_stock")
 			if args.get("doctype") in ["Sales Invoice", "Purchase Invoice"]
 			else 0,
@@ -378,6 +377,10 @@
 		}
 	)
 
+	default_supplier = get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults)
+	if default_supplier:
+		out.supplier = default_supplier
+
 	if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
 		out.update(calculate_service_end_date(args, item))
 
@@ -572,8 +575,8 @@
 			item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out)
 			item_group = item_group_doc.parent_item_group
 
-	if args.child_doctype and item_tax_template:
-		out.update(get_fetch_values(args.child_doctype, "item_tax_template", item_tax_template))
+	if args.get("child_doctype") and item_tax_template:
+		out.update(get_fetch_values(args.get("child_doctype"), "item_tax_template", item_tax_template))
 
 
 def _get_item_tax_template(args, taxes, out=None, for_validate=False):
diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
index 176a215..7f2608e 100644
--- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
+++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
@@ -4,7 +4,7 @@
 
 import frappe
 from frappe import _
-from frappe.utils import cint, flt, getdate
+from frappe.utils import cint, flt, get_table_name, getdate
 from frappe.utils.deprecations import deprecated
 from pypika import functions as fn
 
@@ -13,11 +13,22 @@
 SLE_COUNT_LIMIT = 10_000
 
 
+def _estimate_table_row_count(doctype: str):
+	table = get_table_name(doctype)
+	return cint(
+		frappe.db.sql(
+			f"""select table_rows
+			   from  information_schema.tables
+			   where table_name = '{table}' ;"""
+		)[0][0]
+	)
+
+
 def execute(filters=None):
 	if not filters:
 		filters = {}
 
-	sle_count = frappe.db.count("Stock Ledger Entry")
+	sle_count = _estimate_table_row_count("Stock Ledger Entry")
 
 	if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"):
 		frappe.throw(_("Please select either the Item or Warehouse filter to generate the report."))
diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.js b/erpnext/stock/report/reserved_stock/reserved_stock.js
index 6872741..2b075e2 100644
--- a/erpnext/stock/report/reserved_stock/reserved_stock.js
+++ b/erpnext/stock/report/reserved_stock/reserved_stock.js
@@ -149,34 +149,36 @@
 	formatter: (value, row, column, data, default_formatter) => {
 		value = default_formatter(value, row, column, data);
 
-		if (column.fieldname == "status") {
-			switch (data.status) {
-				case "Partially Reserved":
-					value = "<span style='color:orange'>" + value + "</span>";
-					break;
-				case "Reserved":
-					value = "<span style='color:blue'>" + value + "</span>";
-					break;
-				case "Partially Delivered":
-					value = "<span style='color:purple'>" + value + "</span>";
-					break;
-				case "Delivered":
-					value = "<span style='color:green'>" + value + "</span>";
-					break;
+		if (data) {
+			if (column.fieldname == "status") {
+				switch (data.status) {
+					case "Partially Reserved":
+						value = "<span style='color:orange'>" + value + "</span>";
+						break;
+					case "Reserved":
+						value = "<span style='color:blue'>" + value + "</span>";
+						break;
+					case "Partially Delivered":
+						value = "<span style='color:purple'>" + value + "</span>";
+						break;
+					case "Delivered":
+						value = "<span style='color:green'>" + value + "</span>";
+						break;
+				}
 			}
-		}
-		else if (column.fieldname == "delivered_qty") {
-			if (data.delivered_qty > 0) {
-				if (data.reserved_qty > data.delivered_qty) {
-					value = "<span style='color:blue'>" + value + "</span>";
+			else if (column.fieldname == "delivered_qty") {
+				if (data.delivered_qty > 0) {
+					if (data.reserved_qty > data.delivered_qty) {
+						value = "<span style='color:blue'>" + value + "</span>";
+					}
+					else {
+						value = "<span style='color:green'>" + value + "</span>";
+					}
 				}
 				else {
-					value = "<span style='color:green'>" + value + "</span>";
+					value = "<span style='color:red'>" + value + "</span>";
 				}
 			}
-			else {
-				value = "<span style='color:red'>" + value + "</span>";
-			}
 		}
 
 		return value;
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
index ae12fbb..810dc46 100644
--- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
@@ -1,9 +1,12 @@
 # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
+import copy
+
 import frappe
 from frappe import _
 
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_serial_nos_from_sle
 from erpnext.stock.stock_ledger import get_stock_ledger_entries
 
 
@@ -15,8 +18,8 @@
 
 def get_columns(filters):
 	columns = [
-		{"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date"},
-		{"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time"},
+		{"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date", "width": 120},
+		{"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time", "width": 90},
 		{
 			"label": _("Voucher Type"),
 			"fieldtype": "Link",
@@ -29,7 +32,7 @@
 			"fieldtype": "Dynamic Link",
 			"fieldname": "voucher_no",
 			"options": "voucher_type",
-			"width": 180,
+			"width": 230,
 		},
 		{
 			"label": _("Company"),
@@ -49,7 +52,7 @@
 			"label": _("Status"),
 			"fieldtype": "Data",
 			"fieldname": "status",
-			"width": 120,
+			"width": 90,
 		},
 		{
 			"label": _("Serial No"),
@@ -62,7 +65,7 @@
 			"label": _("Valuation Rate"),
 			"fieldtype": "Float",
 			"fieldname": "valuation_rate",
-			"width": 150,
+			"width": 130,
 		},
 		{
 			"label": _("Qty"),
@@ -102,15 +105,29 @@
 			}
 		)
 
-		serial_nos = [{"serial_no": row.serial_no, "valuation_rate": row.valuation_rate}]
+		serial_nos = []
+		if row.serial_no:
+			parsed_serial_nos = get_serial_nos_from_sle(row.serial_no)
+			for serial_no in parsed_serial_nos:
+				if filters.get("serial_no") and filters.get("serial_no") != serial_no:
+					continue
+
+				serial_nos.append(
+					{
+						"serial_no": serial_no,
+						"valuation_rate": abs(row.stock_value_difference / row.actual_qty),
+					}
+				)
+
 		if row.serial_and_batch_bundle:
-			serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, [])
+			serial_nos.extend(bundle_wise_serial_nos.get(row.serial_and_batch_bundle, []))
 
 		for index, bundle_data in enumerate(serial_nos):
 			if index == 0:
-				args.serial_no = bundle_data.get("serial_no")
-				args.valuation_rate = bundle_data.get("valuation_rate")
-				data.append(args)
+				new_args = copy.deepcopy(args)
+				new_args.serial_no = bundle_data.get("serial_no")
+				new_args.valuation_rate = bundle_data.get("valuation_rate")
+				data.append(new_args)
 			else:
 				data.append(
 					{
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.js b/erpnext/stock/report/stock_analytics/stock_analytics.js
index ea7bf56..e033fd9 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.js
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.js
@@ -17,6 +17,7 @@
 			fieldtype: "Link",
 			options:"Item",
 			default: "",
+			get_query: () => ({filters: { 'is_stock_item': 1 }}),
 		},
 		{
 			fieldname: "value_quantity",
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py
index 6c5b58c..ab48181 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.py
@@ -270,7 +270,7 @@
 	if item_code := filters.get("item_code"):
 		return [item_code]
 	else:
-		item_filters = {}
+		item_filters = {"is_stock_item": 1}
 		if item_group := filters.get("item_group"):
 			children = get_descendants_of("Item Group", item_group, ignore_permissions=True)
 			item_filters["item_group"] = ("in", children + [item_group])
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index a59f9de..ed84a5c 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -413,7 +413,7 @@
 					"fieldname": "bal_val",
 					"fieldtype": "Currency",
 					"width": 100,
-					"options": "currency",
+					"options": "Company:company:default_currency",
 				},
 				{
 					"label": _("Opening Qty"),
@@ -427,7 +427,7 @@
 					"fieldname": "opening_val",
 					"fieldtype": "Currency",
 					"width": 110,
-					"options": "currency",
+					"options": "Company:company:default_currency",
 				},
 				{
 					"label": _("In Qty"),
diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js
index b1e4a74..bf3a397 100644
--- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js
+++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js
@@ -14,9 +14,17 @@
 frappe.query_reports["Stock Ledger Variance"] = {
 	"filters": [
 		{
+			"fieldname": "company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"reqd": 1,
+			"default": frappe.defaults.get_user_default("Company")
+		},
+		{
 			"fieldname": "item_code",
 			"fieldtype": "Link",
-			"label": "Item",
+			"label": __("Item"),
 			"options": "Item",
 			get_query: function() {
 				return {
@@ -27,7 +35,7 @@
 		{
 			"fieldname": "warehouse",
 			"fieldtype": "Link",
-			"label": "Warehouse",
+			"label": __("Warehouse"),
 			"options": "Warehouse",
 			get_query: function() {
 				return {
@@ -38,7 +46,7 @@
 		{
 			"fieldname": "difference_in",
 			"fieldtype": "Select",
-			"label": "Difference In",
+			"label": __("Difference In"),
 			"options": [
 				"",
 				"Qty",
@@ -49,7 +57,7 @@
 		{
 			"fieldname": "include_disabled",
 			"fieldtype": "Check",
-			"label": "Include Disabled",
+			"label": __("Include Disabled"),
 		}
 	],
 
diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py
index 732f108..189a90a 100644
--- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py
+++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py
@@ -56,6 +56,11 @@
 			"options": "Warehouse",
 		},
 		{
+			"fieldname": "valuation_method",
+			"fieldtype": "Data",
+			"label": _("Valuation Method"),
+		},
+		{
 			"fieldname": "voucher_type",
 			"fieldtype": "Link",
 			"label": _("Voucher Type"),
@@ -194,6 +199,7 @@
 def get_data(filters=None):
 	filters = frappe._dict(filters or {})
 	item_warehouse_map = get_item_warehouse_combinations(filters)
+	valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
 
 	data = []
 	if item_warehouse_map:
@@ -206,8 +212,17 @@
 				continue
 
 			for row in report_data:
-				if has_difference(row, precision, filters.difference_in):
-					data.append(add_item_warehouse_details(row, item_warehouse))
+				if has_difference(
+					row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method
+				):
+					row.update(
+						{
+							"item_code": item_warehouse.item_code,
+							"warehouse": item_warehouse.warehouse,
+							"valuation_method": item_warehouse.valuation_method or valuation_method,
+						}
+					)
+					data.append(row)
 					break
 
 	return data
@@ -229,8 +244,14 @@
 		.select(
 			bin.item_code,
 			bin.warehouse,
+			item.valuation_method,
 		)
-		.where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0))
+		.where(
+			(item.is_stock_item == 1)
+			& (item.has_serial_no == 0)
+			& (warehouse.is_group == 0)
+			& (warehouse.company == filters.company)
+		)
 	)
 
 	if filters.item_code:
@@ -243,37 +264,27 @@
 	return query.run(as_dict=1)
 
 
-def has_difference(row, precision, difference_in):
-	has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
-	has_value_difference = (
-		flt(row.diff_value_diff, precision)
-		or flt(row.fifo_value_diff, precision)
-		or flt(row.fifo_difference_diff, precision)
-	)
-	has_valuation_difference = flt(row.valuation_diff, precision) or flt(
-		row.fifo_valuation_diff, precision
-	)
+def has_difference(row, precision, difference_in, valuation_method):
+	if valuation_method == "Moving Average":
+		qty_diff = flt(row.difference_in_qty, precision)
+		value_diff = flt(row.diff_value_diff, precision)
+		valuation_diff = flt(row.valuation_diff, precision)
+	else:
+		qty_diff = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
+		value_diff = (
+			flt(row.diff_value_diff, precision)
+			or flt(row.fifo_value_diff, precision)
+			or flt(row.fifo_difference_diff, precision)
+		)
+		valuation_diff = flt(row.valuation_diff, precision) or flt(row.fifo_valuation_diff, precision)
 
-	if difference_in == "Qty" and has_qty_difference:
+	if difference_in == "Qty" and qty_diff:
 		return True
-	elif difference_in == "Value" and has_value_difference:
+	elif difference_in == "Value" and value_diff:
 		return True
-	elif difference_in == "Valuation" and has_valuation_difference:
+	elif difference_in == "Valuation" and valuation_diff:
 		return True
 	elif difference_in not in ["Qty", "Value", "Valuation"] and (
-		has_qty_difference or has_value_difference or has_valuation_difference
+		qty_diff or value_diff or valuation_diff
 	):
 		return True
-
-	return False
-
-
-def add_item_warehouse_details(row, item_warehouse):
-	row.update(
-		{
-			"item_code": item_warehouse.item_code,
-			"warehouse": item_warehouse.warehouse,
-		}
-	)
-
-	return row
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index de28be1..a1874b8 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -218,15 +218,16 @@
 			).validate_serial_and_batch_inventory()
 
 	def post_process(self):
-		if not self.sle.serial_and_batch_bundle:
+		if not self.sle.serial_and_batch_bundle and not self.sle.serial_no and not self.sle.batch_no:
 			return
 
-		docstatus = frappe.get_cached_value(
-			"Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "docstatus"
-		)
+		if self.sle.serial_and_batch_bundle:
+			docstatus = frappe.get_cached_value(
+				"Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "docstatus"
+			)
 
-		if docstatus != 1:
-			self.submit_serial_and_batch_bundle()
+			if docstatus != 1:
+				self.submit_serial_and_batch_bundle()
 
 		if self.item_details.has_serial_no == 1:
 			self.set_warehouse_and_status_in_serial_nos()
@@ -249,7 +250,12 @@
 		doc.submit()
 
 	def set_warehouse_and_status_in_serial_nos(self):
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_parsed_serial_nos
+
 		serial_nos = get_serial_nos(self.sle.serial_and_batch_bundle)
+		if not self.sle.serial_and_batch_bundle and self.sle.serial_no:
+			serial_nos = get_parsed_serial_nos(self.sle.serial_no)
+
 		warehouse = self.warehouse if self.sle.actual_qty > 0 else None
 
 		if not serial_nos:
@@ -263,7 +269,14 @@
 		(
 			frappe.qb.update(sn_table)
 			.set(sn_table.warehouse, warehouse)
-			.set(sn_table.status, "Active" if warehouse else status)
+			.set(
+				sn_table.status,
+				"Active"
+				if warehouse
+				else status
+				if (sn_table.purchase_document_no != self.sle.voucher_no and self.sle.is_cancelled != 1)
+				else "Inactive",
+			)
 			.where(sn_table.name.isin(serial_nos))
 		).run()
 
@@ -290,6 +303,8 @@
 		from erpnext.stock.doctype.batch.batch import get_available_batches
 
 		batches = get_batch_nos(self.sle.serial_and_batch_bundle)
+		if not self.sle.serial_and_batch_bundle and self.sle.batch_no:
+			batches = frappe._dict({self.sle.batch_no: self.sle.actual_qty})
 
 		batches_qty = get_available_batches(
 			frappe._dict(
@@ -312,13 +327,35 @@
 	if serial_nos:
 		filters["serial_no"] = ("in", serial_nos)
 
-	entries = frappe.get_all("Serial and Batch Entry", fields=["serial_no"], filters=filters)
+	entries = frappe.get_all(
+		"Serial and Batch Entry", fields=["serial_no"], filters=filters, order_by="idx"
+	)
 	if not entries:
 		return []
 
 	return [d.serial_no for d in entries if d.serial_no]
 
 
+def get_batches_from_bundle(serial_and_batch_bundle, batches=None):
+	if not serial_and_batch_bundle:
+		return []
+
+	filters = {"parent": serial_and_batch_bundle, "batch_no": ("is", "set")}
+	if isinstance(serial_and_batch_bundle, list):
+		filters = {"parent": ("in", serial_and_batch_bundle)}
+
+	if batches:
+		filters["batch_no"] = ("in", batches)
+
+	entries = frappe.get_all(
+		"Serial and Batch Entry", fields=["batch_no", "qty"], filters=filters, order_by="idx", as_list=1
+	)
+	if not entries:
+		return frappe._dict({})
+
+	return frappe._dict(entries)
+
+
 def get_serial_nos_from_bundle(serial_and_batch_bundle, serial_nos=None):
 	return get_serial_nos(serial_and_batch_bundle, serial_nos=serial_nos)
 
@@ -406,7 +443,7 @@
 			.orderby(bundle.posting_date, bundle.posting_time, bundle.creation)
 		)
 
-		# Important to exclude the current voucher
+		# Important to exclude the current voucher to calculate correct the stock value difference
 		if self.sle.voucher_no:
 			query = query.where(bundle.voucher_no != self.sle.voucher_no)
 
@@ -539,8 +576,10 @@
 			.groupby(child.batch_no)
 		)
 
-		# Important to exclude the current voucher
-		if self.sle.voucher_no:
+		# Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference
+		if self.sle.voucher_detail_no:
+			query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no)
+		elif self.sle.voucher_no:
 			query = query.where(parent.voucher_no != self.sle.voucher_no)
 
 		if timestamp_condition:
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 9142a27..a6206ac 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -25,6 +25,7 @@
 )
 from erpnext.stock.utils import (
 	get_incoming_outgoing_rate_for_cancel,
+	get_incoming_rate,
 	get_or_make_bin,
 	get_stock_balance,
 	get_valuation_method,
@@ -841,14 +842,33 @@
 					get_rate_for_return,  # don't move this import to top
 				)
 
-				rate = get_rate_for_return(
-					sle.voucher_type,
-					sle.voucher_no,
-					sle.item_code,
-					voucher_detail_no=sle.voucher_detail_no,
-					sle=sle,
-				)
+				if self.valuation_method == "Moving Average":
+					rate = get_incoming_rate(
+						{
+							"item_code": sle.item_code,
+							"warehouse": sle.warehouse,
+							"posting_date": sle.posting_date,
+							"posting_time": sle.posting_time,
+							"qty": sle.actual_qty,
+							"serial_no": sle.get("serial_no"),
+							"batch_no": sle.get("batch_no"),
+							"serial_and_batch_bundle": sle.get("serial_and_batch_bundle"),
+							"company": sle.company,
+							"voucher_type": sle.voucher_type,
+							"voucher_no": sle.voucher_no,
+							"allow_zero_valuation": self.allow_zero_rate,
+							"sle": sle.name,
+						}
+					)
 
+				else:
+					rate = get_rate_for_return(
+						sle.voucher_type,
+						sle.voucher_no,
+						sle.item_code,
+						voucher_detail_no=sle.voucher_detail_no,
+						sle=sle,
+					)
 			elif (
 				sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
 				and sle.voucher_detail_no
@@ -1711,7 +1731,7 @@
 def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
 	if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code):
 		return
-	if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"):
+	if args.actual_qty >= 0 and args.voucher_type != "Stock Reconciliation":
 		return
 
 	neg_sle = get_future_sle_with_negative_qty(args)
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
index 6c187f8..0fe8c13 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
@@ -7,6 +7,7 @@
 from frappe.utils import flt
 
 from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created
+from erpnext.buying.doctype.purchase_order.purchase_order import update_status as update_po_status
 from erpnext.controllers.subcontracting_controller import SubcontractingController
 from erpnext.stock.stock_balance import update_bin_qty
 from erpnext.stock.utils import get_bin
@@ -308,6 +309,9 @@
 				"Subcontracting Order", self.name, "status", status, update_modified=update_modified
 			)
 
+			if status == "Closed":
+				update_po_status("Closed", self.purchase_order)
+
 
 @frappe.whitelist()
 def make_subcontracting_receipt(source_name, target_doc=None):
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 1a5deb6..52bf13c 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -167,13 +167,13 @@
 		)
 		self.update_status_updater_args()
 		self.update_prevdoc_status()
-		self.delete_auto_created_batches()
 		self.set_consumed_qty_in_subcontract_order()
 		self.set_subcontracting_order_status()
 		self.update_stock_ledger()
 		self.make_gl_entries_on_cancel()
 		self.repost_future_sle_and_gle()
 		self.update_status()
+		self.delete_auto_created_batches()
 
 	def validate_items_qty(self):
 		for item in self.items:
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
index 1d007fe..9d7be36 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
@@ -365,24 +365,17 @@
 		fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse)
 		supplier_warehouse_ac = get_inventory_account(scr.company, scr.supplier_warehouse)
 		expense_account = scr.items[0].expense_account
+		expected_values = [
+			[fg_warehouse_ac, 2100.0, 0.0],  # FG Amount (D)
+			[supplier_warehouse_ac, 0.0, 1000.0],  # RM Cost (C)
+			[additional_costs_expense_account, 0.0, 100.0],  # Additional Cost (C)
+			[expense_account, 0.0, 1000.0],  # Service Cost (C)
+		]
 
-		if fg_warehouse_ac == supplier_warehouse_ac:
-			expected_values = {
-				fg_warehouse_ac: [2100.0, 1000.0],  # FG Amount (D), RM Cost (C)
-				expense_account: [0.0, 1000.0],  # Service Cost (C)
-				additional_costs_expense_account: [0.0, 100.0],  # Additional Cost (C)
-			}
-		else:
-			expected_values = {
-				fg_warehouse_ac: [2100.0, 0.0],  # FG Amount (D)
-				supplier_warehouse_ac: [0.0, 1000.0],  # RM Cost (C)
-				expense_account: [0.0, 1000.0],  # Service Cost (C)
-				additional_costs_expense_account: [0.0, 100.0],  # Additional Cost (C)
-			}
-
-		for gle in gl_entries:
-			self.assertEqual(expected_values[gle.account][0], gle.debit)
-			self.assertEqual(expected_values[gle.account][1], gle.credit)
+		for i in range(len(expected_values)):
+			self.assertEqual(expected_values[i][0], gl_entries[i]["account"])
+			self.assertEqual(expected_values[i][1], gl_entries[i]["debit"])
+			self.assertEqual(expected_values[i][2], gl_entries[i]["credit"])
 
 		scr.reload()
 		scr.cancel()
@@ -953,6 +946,91 @@
 
 		scr.submit()
 
+	def test_subcontracting_receipt_cancel_with_batch(self):
+		from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+		# Step - 1: Set Backflush Based On as "BOM"
+		set_backflush_based_on("BOM")
+
+		# Step - 2: Create FG and RM Items
+		fg_item = make_item(
+			properties={"is_stock_item": 1, "is_sub_contracted_item": 1, "has_batch_no": 1}
+		).name
+		rm_item1 = make_item(properties={"is_stock_item": 1}).name
+		rm_item2 = make_item(properties={"is_stock_item": 1}).name
+		make_item("Subcontracted Service Item Test For Batch 1", {"is_stock_item": 0})
+
+		# Step - 3: Create BOM for FG Item
+		bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2])
+		for rm_item in bom.items:
+			self.assertEqual(rm_item.rate, 0)
+			self.assertEqual(rm_item.amount, 0)
+		bom = bom.name
+
+		# Step - 4: Create PO and SCO
+		service_items = [
+			{
+				"warehouse": "_Test Warehouse - _TC",
+				"item_code": "Subcontracted Service Item Test For Batch 1",
+				"qty": 100,
+				"rate": 100,
+				"fg_item": fg_item,
+				"fg_item_qty": 100,
+			},
+		]
+		sco = get_subcontracting_order(service_items=service_items)
+		for rm_item in sco.supplied_items:
+			self.assertEqual(rm_item.rate, 0)
+			self.assertEqual(rm_item.amount, 0)
+
+		# Step - 5: Inward Raw Materials
+		rm_items = get_rm_items(sco.supplied_items)
+		for rm_item in rm_items:
+			rm_item["rate"] = 100
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+
+		# Step - 6: Transfer RM's to Subcontractor
+		se = make_stock_transfer_entry(
+			sco_no=sco.name,
+			rm_items=rm_items,
+			itemwise_details=copy.deepcopy(itemwise_details),
+		)
+		for item in se.items:
+			self.assertEqual(item.qty, 100)
+			self.assertEqual(item.basic_rate, 100)
+			self.assertEqual(item.amount, item.qty * item.basic_rate)
+
+		batch_doc = frappe.get_doc(
+			{
+				"doctype": "Batch",
+				"item": fg_item,
+				"batch_id": frappe.generate_hash(length=10),
+			}
+		).insert(ignore_permissions=True)
+
+		serial_batch_bundle = frappe.get_doc(
+			{
+				"doctype": "Serial and Batch Bundle",
+				"item_code": fg_item,
+				"warehouse": sco.items[0].warehouse,
+				"has_batch_no": 1,
+				"type_of_transaction": "Inward",
+				"voucher_type": "Subcontracting Receipt",
+				"entries": [{"batch_no": batch_doc.name, "qty": 100}],
+			}
+		).insert(ignore_permissions=True)
+
+		# Step - 7: Create Subcontracting Receipt
+		scr = make_subcontracting_receipt(sco.name)
+		scr.items[0].serial_and_batch_bundle = serial_batch_bundle.name
+		scr.save()
+		scr.submit()
+		scr.load_from_db()
+
+		# Step - 8: Cancel Subcontracting Receipt
+		scr.cancel()
+		self.assertTrue(scr.docstatus == 2)
+
 	@change_settings("Buying Settings", {"auto_create_purchase_receipt": 1})
 	def test_auto_create_purchase_receipt(self):
 		fg_item = "Subcontracted Item SA1"
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index f96823b..9f91dc1 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -58,7 +58,9 @@
 
 				frappe.call("erpnext.support.doctype.service_level_agreement.service_level_agreement.reset_service_level_agreement", {
 					reason: values.reason,
-					user: frappe.session.user_email
+					user: frappe.session.user_email,
+					doctype: frm.doc.doctype,
+					docname: frm.doc.name,
 				}, () => {
 					reset_sla.enable_primary_action();
 					frm.refresh();
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index 879381c..f6b3a13 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -100,7 +100,7 @@
 			priorities.append(priority.priority)
 
 		# Check if repeated priority
-		if not len(set(priorities)) == len(priorities):
+		if len(set(priorities)) != len(priorities):
 			repeated_priority = get_repeated(priorities)
 			frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority))
 
@@ -128,7 +128,7 @@
 				)
 
 		# Check for repeated workday
-		if not len(set(support_days)) == len(support_days):
+		if len(set(support_days)) != len(support_days):
 			repeated_days = get_repeated(support_days)
 			frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
 
@@ -748,13 +748,13 @@
 		and frappe.db.get_single_value("Support Settings", "track_service_level_agreement")
 	):
 
-		if not self.priority == frappe.db.get_value("Issue", self.name, "priority"):
+		if self.priority != frappe.db.get_value("Issue", self.name, "priority"):
 			self.set_response_and_resolution_time(
 				priority=self.priority, service_level_agreement=self.service_level_agreement
 			)
 			frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority))
 
-		if not self.service_level_agreement == frappe.db.get_value(
+		if self.service_level_agreement != frappe.db.get_value(
 			"Issue", self.name, "service_level_agreement"
 		):
 			self.set_response_and_resolution_time(
@@ -774,10 +774,12 @@
 	return priority
 
 
-def reset_service_level_agreement(doc, reason, user):
+@frappe.whitelist()
+def reset_service_level_agreement(doctype: str, docname: str, reason, user):
 	if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
 		frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
 
+	doc = frappe.get_doc(doctype, docname)
 	frappe.get_doc(
 		{
 			"doctype": "Comment",
diff --git a/erpnext/templates/pages/home.html b/erpnext/templates/pages/home.html
index 08e0432..b9b435c 100644
--- a/erpnext/templates/pages/home.html
+++ b/erpnext/templates/pages/home.html
@@ -26,6 +26,26 @@
 		{{ render_homepage_section(homepage.hero_section_doc) }}
 	{% endif %}
 
+	{% if homepage.products %}
+	<section class="container section-products my-5">
+		<h3>{{ _('Products') }}</h3>
+
+		<div class="row">
+			{% for item in homepage.products %}
+			<div class="col-md-4 mb-4">
+				<div class="card h-100 justify-content-between">
+					<img class="card-img-top website-image-extra-large" src="{{ item.image }}" loading="lazy" alt="{{ item.item_name }}"></img>
+					<div class="card-body flex-grow-0">
+						<h5 class="card-title">{{ item.item_name }}</h5>
+						<a href="{{ item.route }}" class="card-link">{{ _('More details') }}</a>
+					</div>
+				</div>
+			</div>
+			{% endfor %}
+		</div>
+	</section>
+	{% endif %}
+
 	{% if blogs %}
 	<section class="container my-5">
 		<h3>{{ _('Publications') }}</h3>
diff --git a/erpnext/tests/test_perf.py b/erpnext/tests/test_perf.py
new file mode 100644
index 0000000..fc17b1d
--- /dev/null
+++ b/erpnext/tests/test_perf.py
@@ -0,0 +1,24 @@
+import frappe
+from frappe.tests.utils import FrappeTestCase
+
+INDEXED_FIELDS = {
+	"Bin": ["item_code"],
+	"GL Entry": ["voucher_type", "against_voucher_type"],
+	"Purchase Order Item": ["item_code"],
+	"Stock Ledger Entry": ["warehouse"],
+}
+
+
+class TestPerformance(FrappeTestCase):
+	def test_ensure_indexes(self):
+		# These fields are not explicitly indexed BUT they are prefix in some
+		# other composite index. If those are removed this test should be
+		# updated accordingly.
+		for doctype, fields in INDEXED_FIELDS.items():
+			for field in fields:
+				self.assertTrue(
+					frappe.db.sql(
+						f"""SHOW INDEX FROM `tab{doctype}`
+						WHERE Column_name = "{field}" AND Seq_in_index = 1"""
+					)
+				)
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 73755be..d05d0d9 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -103,6 +103,7 @@
 Actual Qty {0} / Waiting Qty {1},Tatsächliche Menge {0} / Wartezeit {1},
 Actual Qty: Quantity available in the warehouse.,Tatsächliche Menge: Menge verfügbar im Lager.,
 Actual qty in stock,Tatsächliche Menge auf Lager,
+Actual Time (in Hours via Time Sheet), IST Zeit (in Stunden aus Zeiterfassung),
 Actual type tax cannot be included in Item rate in row {0},Tatsächliche Steuerart kann nicht im Artikelpreis in Zeile {0} beinhaltet sein,
 Add,Hinzufügen,
 Add / Edit Prices,Preise hinzufügen / bearbeiten,
@@ -8856,3 +8857,7 @@
 Split Early Payment Discount Loss into Income and Tax Loss,"Skontobetrag in Aufwand und Umsatzsteuerkorrektur aufteilen",
 Approve,Genehmigen,
 Reject,Ablehnen,
+Lost Quotations,Verlorene Angebote,
+Lost Quotations %,Verlorene Angebote %,
+Lost Value,Verlorener Wert,
+Lost Value %,Verlorener Wert %,
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 5c34759..a711123 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -134,7 +134,7 @@
 Advertising,Publicité,
 Aerospace,Aérospatial,
 Against,Contre,
-Against Account,Pour le Compte,
+Against Account,Contrepartie,
 Against Journal Entry {0} does not have any unmatched {1} entry,L'Écriture de Journal {0} n'a pas d'entrée non associée {1},
 Against Journal Entry {0} is already adjusted against some other voucher,L'Écriture de Journal {0} est déjà ajustée par un autre bon,
 Against Supplier Invoice {0} dated {1},Pour la Facture Fournisseur {0} datée {1},
@@ -2969,7 +2969,7 @@
 Please save first,S'il vous plaît enregistrer en premier,
 Price not found for item {0} in price list {1},Prix non trouvé pour l'article {0} dans la liste de prix {1},
 Warehouse Type,Type d'entrepôt,
-'Date' is required,'Date' est requis,
+'Date' is required,La 'date' est obligatoire,
 Budgets,Budgets,
 Bundle Qty,Quantité de paquet,
 Company GSTIN,GSTIN de la Société,
@@ -3002,7 +3002,7 @@
 Account: {0} is not permitted under Payment Entry,Compte: {0} n'est pas autorisé sous Saisie du paiement.,
 Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.,La dimension de comptabilité <b>{0}</b> est requise pour le compte &quot;Bilan&quot; {1}.,
 Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.,La dimension de comptabilité <b>{0}</b> est requise pour le compte 'Bénéfices et pertes' {1}.,
-Accounting Masters,Maîtres Comptables,
+Accounting Masters,Données de base,
 Accounting Period overlaps with {0},La période comptable chevauche avec {0},
 Add to Featured Item,Ajouter à l'article en vedette,
 Add your review,Ajouter votre avis,
@@ -3701,7 +3701,7 @@
 Accounting Period,Période comptable,
 Period Name,Nom de période,
 Closed Documents,Documents fermés,
-Accounts Settings,Paramètres des Comptes,
+Accounts Settings,Paramètres de comptabilité,
 Settings for Accounts,Paramètres des Comptes,
 Make Accounting Entry For Every Stock Movement,Faites une Écriture Comptable Pour Chaque Mouvement du Stock,
 Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,Les utilisateurs ayant ce rôle sont autorisés à définir les comptes gelés et à créer / modifier des écritures comptables sur des comptes gelés,
@@ -3718,7 +3718,7 @@
 Currency Exchange Settings,Paramètres d'échange de devises,
 Allow Stale Exchange Rates,Autoriser les Taux de Change Existants,
 Stale Days,Journées Passées,
-Report Settings,Paramètres de rapport,
+Report Settings,Paramètres des rapports,
 Use Custom Cash Flow Format,Utiliser le format de flux de trésorerie personnalisé,
 Allowed To Transact With,Autorisé à faire affaire avec,
 SWIFT number,Numéro rapide,
@@ -4321,7 +4321,7 @@
 Redemption Cost Center,Centre de coûts pour l'échange,
 In Words will be visible once you save the Sales Invoice.,En Toutes Lettres. Sera visible une fois que vous enregistrerez la Facture.,
 Allocate Advances Automatically (FIFO),Allouer automatiquement les avances (FIFO),
-Get Advances Received,Obtenir Acomptes Reçus,
+Get Advances Received,Obtenir les paiements des avances,
 Base Change Amount (Company Currency),Montant de Base à Rendre (Devise de la Société),
 Write Off Outstanding Amount,Encours de Reprise,
 Terms and Conditions Details,Détails des Termes et Conditions,
@@ -4329,7 +4329,7 @@
 Is Discounted,Est réduit,
 Unpaid and Discounted,Non payé et à prix réduit,
 Overdue and Discounted,En retard et à prix réduit,
-Accounting Details,Détails Comptabilité,
+Accounting Details,Détails Comptable,
 Debit To,Débit Pour,
 Is Opening Entry,Est Écriture Ouverte,
 C-Form Applicable,Formulaire-C Applicable,
@@ -8028,7 +8028,7 @@
 Completed Qty cannot be greater than 'Qty to Manufacture',La quantité terminée ne peut pas être supérieure à la `` quantité à fabriquer '',
 "Row {0}: For Supplier {1}, Email Address is Required to send an email","Ligne {0}: pour le fournisseur {1}, l'adresse e-mail est obligatoire pour envoyer un e-mail",
 "If enabled, the system will post accounting entries for inventory automatically","Si activé, le système enregistrera automatiquement les écritures comptables pour l'inventaire",
-Accounts Frozen Till Date,Comptes gelés jusqu'à la date,
+Accounts Frozen Till Date,Comptes gelés jusqu'au,
 Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below,Les écritures comptables sont gelées jusqu'à cette date. Personne ne peut créer ou modifier des entrées sauf les utilisateurs avec le rôle spécifié ci-dessous,
 Role Allowed to Set Frozen Accounts and Edit Frozen Entries,Rôle autorisé à définir des comptes gelés et à modifier les entrées gelées,
 Address used to determine Tax Category in transactions,Adresse utilisée pour déterminer la catégorie de taxe dans les transactions,
@@ -8339,7 +8339,7 @@
 Unit Of Measure (UOM),Unité de mesure (UdM),
 CRM Settings,Paramètres CRM,
 Do Not Explode,Ne pas décomposer,
-Quick Access, Accés rapides,
+Quick Access, Accès rapides,
 {}  Available,{} Disponible.s,
 {} Pending,{} En attente.s,
 {} To Bill,{} à facturer,
@@ -8449,3 +8449,64 @@
 Allow Sales,Autoriser à la vente
 Approve,Approuver,
 Reject,Rejeter,
+'Account' in the Accounting section of Customer {0},'Compte' dans la section comptabilité du client {0},
+Accounting Entry Number,Numéro d'écriture comptable,
+Accounting Ledger,Grand livre,
+Accounts Closing,Clôture,
+Accounts Frozen Upto,Comptes gelés jusqu'au,
+Accounts Manager,Responsable comptable,
+Active Customers,Clients actifs,
+Against Account,Contrepartie,
+All the Comments and Emails will be copied from one document to another newly created document(Lead -> Opportunity -> Quotation) throughout the CRM documents.,Tous les commentaires et les courriels seront copiés d'un document à un autre document nouvellement créé (Lead -> Opportunité -> Devis) dans l'ensemble des documents CRM.,
+Allow multi-currency invoices against single party account ,Autoriser les factures multi-devises en contrepartie d'un seul compte de tiers,
+Allow Sales Order Creation For Expired Quotation,Autoriser la création de commandes client pour les devis expirés,
+Allow Continuous Material Consumption,Autoriser la consommation continue de matériel,
+Allow Lead Duplication based on Emails,Autoriser la duplication des pistes sur la base des courriels,
+Asset Settings,Paramètres des actifs,
+Auto close Opportunity Replied after the no. of days mentioned above,Fermeture automatique de l'opportunité de réponse après le nombre de jours mentionné ci-dessus.,
+Auto Creation of Contact,Création automatique d'un contact,
+Automatically Fetch Payment Terms from Order,Récupération automatique des conditions de paiement de la commande,
+Bill for Rejected Quantity in Purchase Invoice,Facturation de la quantité rejetée dans la facture d'achat,
+Credit Limit Settings,Paramètres de la limite de crédit,
+Create Ledger Entries for Change Amount,Créer des écritures de grand livre pour modifier le montant,
+Customer Defaults,Valeurs par défaut des clients,
+Calculate Product Bundle Price based on Child Items' Rates,Calculer le prix des ensembles de produits en fonction des tarifs des articles enfants,
+Configure the action to stop the transaction or just warn if the same rate is not maintained.,Configurez une action pour stopper la transaction ou alertez simplement su le prix unitaie n'est pas maintenu.,
+Close Replied Opportunity After Days,Fermer l'opportunité répliquée après des jours,
+Carry Forward Communication and Comments,Reprendre les communications et commentaires,
+Default Down Payment Payable Account,Compte d'acompte fournisseur par défaut,
+Default Down Payment Receivable Account,Compte d'acompte client par défaut,
+Disable Last Purchase Rate,Désactiver le dernier prix d'achat,
+'Default {0} Account' in Company {1},'Compte {0} par défaut' dans la société {1},
+Enable Custom Cash Flow Format,Activation du format de flux de trésorerie personnalisé,
+Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field,Garanti que chaque facture d'achat est associée à un numéro de facture fournisseur unique,
+Enable Common Party Accounting,Activer la comptabilité des tiers communs,
+Enabling this will allow creation of multi-currency invoices against single party account in company currency,L'activation de cette option va permettre la création de factures multi-devises en contrepartie d'un seul compte de tiers en devise de la société,
+Enable Discount Accounting for Selling,Activation de la comptabilité d'escompte pour la vente,
+'Expected Start Date' can not be greater than 'Expected End Date','Date de Début Prévue' ne peut pas être postérieure à 'Date de Fin Prévue',
+Get Advances Received, Obtenir les paiements des avances,
+"If enabled, ledger entries will be posted for change amount in POS transactions","Si cette option est activée, des écritures de grand livre seront enregistrées pour le montant de la modification dans les transactions POS.",
+"If enabled, additional ledger entries will be made for discounts in a separate Discount Account","Si cette option est activée, des écritures de grand livre supplémentaires seront effectuées pour les remises dans un compte de remise séparé.",
+Item Price Settings,Paramètres du prix de l'article,
+Invoice and Billing,Facturation,
+Invoice Cancellation,Annulation de facture,
+Invoicing Features,Caractéristiques de la facturation,
+Journals,Journaux,
+"Learn about <a href=""https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier."">Common Party</a>","En savoir plus <a href=""https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier."">Tiers communs</a>",
+Naming Series and Price Defaults,Nom de série et Tarifs,
+Over Order Allowance (%),Tolérance de sur-commande (%),
+Payment Terms from orders will be fetched into the invoices as is,Les termes de paiement des commandes seront récupérées dans les factures telles quelles,
+Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.,"Percentage de commande autorisée en plus de la quantité prévue dans la commande ouverte. Par exemple: Si vous avez une commande avec une quantité de 100 unités et une tolérance de 10%, alors vous pourrez commander jusqu'à 110 unités.",
+Period Closing Settings,Paramètres de clôture de la période,
+Quick Access,Accès rapide,
+Report Setting,Réglage des rapports,
+Record all transactions against an accounting journal,Comptabiliser toutes les transactions dans un journal comptable,
+Rows with Same Account heads will be merged on Ledger,Les lignes associées aux mêmes comptes comptables seront fusionnées dans le grand livre,
+Role Allowed to Over Bill ,Rôle autorisé à sur-facturer,
+Role allowed to bypass Credit Limit,Rôle autorisé à contourner la limite de crédit,
+Role Allowed to Override Stop Action,Rôle autorisé à outrepasser l'action Stop,
+Show Balances in Chart Of Accounts,Afficher les soldes dans le plan comptable,
+Sales Update Frequency in Company and Project,Fréquence de mise à jour des ventes dans la société et le projet,
+Transaction Settings,Paramètres des transactions,
+Subcontracting Settings,Paramètres de sous-traitance,
+Users with this role are allowed to over bill above the allowance percentage,Les utilisateurs avec ce rôle sont autorisés à sur-facturer au delà du pourcentage de tolérance,
diff --git a/erpnext/utilities/doctype/sms_log/README.md b/erpnext/utilities/doctype/sms_log/README.md
deleted file mode 100644
index 9ee2b79..0000000
--- a/erpnext/utilities/doctype/sms_log/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Log of SMS sent via SMS Center.
\ No newline at end of file
diff --git a/erpnext/utilities/doctype/sms_log/sms_log.js b/erpnext/utilities/doctype/sms_log/sms_log.js
deleted file mode 100644
index f5358e8..0000000
--- a/erpnext/utilities/doctype/sms_log/sms_log.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('SMS Log', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/utilities/doctype/sms_log/sms_log.json b/erpnext/utilities/doctype/sms_log/sms_log.json
deleted file mode 100644
index 269094b..0000000
--- a/erpnext/utilities/doctype/sms_log/sms_log.json
+++ /dev/null
@@ -1,371 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "SYS-SMS-.#####", 
- "beta": 0, 
- "creation": "2012-03-27 14:36:47", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "editable_grid": 0, 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "sender_name", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Sender Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "sent_on", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Sent On", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break0", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0, 
-   "width": "50%"
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "message", 
-   "fieldtype": "Small Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Message", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "sec_break1", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Simple", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "no_of_requested_sms", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "No of Requested SMS", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "requested_numbers", 
-   "fieldtype": "Code", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Requested Numbers", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break1", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0, 
-   "width": "50%"
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "no_of_sent_sms", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "No of Sent SMS", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "sent_to", 
-   "fieldtype": "Code", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Sent To", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "icon": "fa fa-mobile-phone", 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2018-08-21 16:15:40.898889", 
- "modified_by": "Administrator", 
- "module": "Utilities", 
- "name": "SMS Log", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 0, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 0, 
-   "submit": 0, 
-   "write": 0
-  }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/utilities/doctype/sms_log/sms_log.py b/erpnext/utilities/doctype/sms_log/sms_log.py
deleted file mode 100644
index 8e4c248..0000000
--- a/erpnext/utilities/doctype/sms_log/sms_log.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-from frappe.model.document import Document
-
-
-class SMSLog(Document):
-	# begin: auto-generated types
-	# This code is auto-generated. Do not modify anything in this block.
-
-	from typing import TYPE_CHECKING
-
-	if TYPE_CHECKING:
-		from frappe.types import DF
-
-		message: DF.SmallText | None
-		no_of_requested_sms: DF.Int
-		no_of_sent_sms: DF.Int
-		requested_numbers: DF.Code | None
-		sender_name: DF.Data | None
-		sent_on: DF.Date | None
-		sent_to: DF.Code | None
-	# end: auto-generated types
-
-	pass
diff --git a/erpnext/utilities/doctype/sms_log/test_sms_log.py b/erpnext/utilities/doctype/sms_log/test_sms_log.py
deleted file mode 100644
index 3ff0202..0000000
--- a/erpnext/utilities/doctype/sms_log/test_sms_log.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-# test_records = frappe.get_test_records('SMS Log')
-
-
-class TestSMSLog(unittest.TestCase):
-	pass