Merge branch 'develop' into stock-reservation
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
index d8880f7..003a43c 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
@@ -53,19 +53,20 @@
 		if "Bank Account" not in json.dumps(preview["columns"]):
 			frappe.throw(_("Please add the Bank Account column"))
 
-		from frappe.utils.background_jobs import is_job_queued
+		from frappe.utils.background_jobs import is_job_enqueued
 		from frappe.utils.scheduler import is_scheduler_inactive
 
 		if is_scheduler_inactive() and not frappe.flags.in_test:
 			frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
 
-		if not is_job_queued(self.name):
+		job_id = f"bank_statement_import::{self.name}"
+		if not is_job_enqueued(job_id):
 			enqueue(
 				start_import,
 				queue="default",
 				timeout=6000,
 				event="data_import",
-				job_name=self.name,
+				job_id=job_id,
 				data_import=self.name,
 				bank_account=self.bank_account,
 				import_file_path=self.import_file,
diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
index 7cd6d04..381083b 100644
--- a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
+++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
@@ -4,7 +4,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils.background_jobs import is_job_queued
+from frappe.utils.background_jobs import is_job_enqueued
 
 from erpnext.accounts.doctype.account.account import merge_account
 
@@ -17,13 +17,14 @@
 		if is_scheduler_inactive() and not frappe.flags.in_test:
 			frappe.throw(_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive"))
 
-		if not is_job_queued(self.name):
+		job_id = f"ledger_merge::{self.name}"
+		if not is_job_enqueued(job_id):
 			enqueue(
 				start_merge,
 				queue="default",
 				timeout=6000,
 				event="ledger_merge",
-				job_name=self.name,
+				job_id=job_id,
 				docname=self.name,
 				now=frappe.conf.developer_mode or frappe.flags.in_test,
 			)
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index 47c2ceb..680afb1 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -6,7 +6,7 @@
 from frappe import _, scrub
 from frappe.model.document import Document
 from frappe.utils import flt, nowdate
-from frappe.utils.background_jobs import enqueue, is_job_queued
+from frappe.utils.background_jobs import enqueue, is_job_enqueued
 
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_accounting_dimensions,
@@ -212,13 +212,15 @@
 			if is_scheduler_inactive() and not frappe.flags.in_test:
 				frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
 
-			if not is_job_queued(self.name):
+			job_id = f"opening_invoice::{self.name}"
+
+			if not is_job_enqueued(job_id):
 				enqueue(
 					start_import,
 					queue="default",
 					timeout=6000,
 					event="opening_invoice_creation",
-					job_name=self.name,
+					job_id=job_id,
 					invoices=invoices,
 					now=frappe.conf.developer_mode or frappe.flags.in_test,
 				)
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index b1e2208..d8aed21 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -9,7 +9,7 @@
 from frappe.model.document import Document
 from frappe.model.mapper import map_child_doc, map_doc
 from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
-from frappe.utils.background_jobs import enqueue, is_job_queued
+from frappe.utils.background_jobs import enqueue, is_job_enqueued
 from frappe.utils.scheduler import is_scheduler_inactive
 
 
@@ -483,15 +483,15 @@
 
 	closing_entry = kwargs.get("closing_entry") or {}
 
-	job_name = closing_entry.get("name")
-	if not is_job_queued(job_name):
+	job_id = "pos_invoice_merge::" + str(closing_entry.get("name"))
+	if not is_job_enqueued(job_id):
 		enqueue(
 			job,
 			**kwargs,
 			queue="long",
 			timeout=10000,
 			event="processing_merge_logs",
-			job_name=job_name,
+			job_id=job_id,
 			now=frappe.conf.developer_mode or frappe.flags.in_test
 		)
 
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 ecb51ce..3166030 100644
--- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py
@@ -164,7 +164,7 @@
 	Fetch queued docs and start reconciliation process for each one
 	"""
 	if not frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"):
-		frappe.throw(
+		frappe.msgprint(
 			_("Auto Reconciliation of Payments has been disabled. Enable it through {0}").format(
 				get_link_to_form("Accounts Settings", "Accounts Settings")
 			)
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 76a01db..8a47e1c 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -546,12 +546,13 @@
 				)
 
 			query = query.where(
-				(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb)]))
+				(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
 				| (gl_entry.finance_book.isnull())
 			)
 		else:
 			query = query.where(
-				(gl_entry.finance_book.isin([cstr(filters.finance_book)])) | (gl_entry.finance_book.isnull())
+				(gl_entry.finance_book.isin([cstr(filters.finance_book), ""]))
+				| (gl_entry.finance_book.isnull())
 			)
 
 	if accounting_dimensions:
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 0b05c11..d47e3da 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -253,14 +253,14 @@
 					_("To use a different finance book, please uncheck 'Include Default Book Entries'")
 				)
 			else:
-				conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)")
+				conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
 		else:
-			conditions.append("(finance_book in (%(company_fb)s) OR finance_book IS NULL)")
+			conditions.append("(finance_book in (%(company_fb)s, '') OR finance_book IS NULL)")
 	else:
 		if filters.get("finance_book"):
-			conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)")
+			conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
 		else:
-			conditions.append("(finance_book IS NULL)")
+			conditions.append("(finance_book in ('') OR finance_book IS NULL)")
 
 	if not filters.get("show_cancelled_entries"):
 		conditions.append("is_cancelled = 0")
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 57dac2a..22bebb7 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -256,12 +256,12 @@
 			)
 
 		opening_balance = opening_balance.where(
-			(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb)]))
+			(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
 			| (closing_balance.finance_book.isnull())
 		)
 	else:
 		opening_balance = opening_balance.where(
-			(closing_balance.finance_book.isin([cstr(filters.finance_book)]))
+			(closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
 			| (closing_balance.finance_book.isnull())
 		)
 
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index a27e348..796a069 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -442,7 +442,29 @@
 			if not dimension:
 				continue
 
-			if row.get(dimension.source_fieldname):
+			if self.doctype in [
+				"Purchase Invoice",
+				"Purchase Receipt",
+				"Sales Invoice",
+				"Delivery Note",
+				"Stock Entry",
+			]:
+				if (sl_dict.actual_qty > 0 and self.doctype in ["Purchase Invoice", "Purchase Receipt"]) or (
+					sl_dict.actual_qty < 0 and self.doctype in ["Sales Invoice", "Delivery Note", "Stock Entry"]
+				):
+					sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
+				else:
+					fieldname_start_with = "to"
+					if self.doctype in ["Purchase Invoice", "Purchase Receipt"]:
+						fieldname_start_with = "from"
+
+					fieldname = f"{fieldname_start_with}_{dimension.source_fieldname}"
+					sl_dict[dimension.target_fieldname] = row.get(fieldname)
+
+					if not sl_dict.get(dimension.target_fieldname):
+						sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
+
+			elif row.get(dimension.source_fieldname):
 				sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
 
 			if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent:
diff --git a/erpnext/e_commerce/web_template/hero_slider/hero_slider.json b/erpnext/e_commerce/web_template/hero_slider/hero_slider.json
index 2b1807c..39b2b3e 100644
--- a/erpnext/e_commerce/web_template/hero_slider/hero_slider.json
+++ b/erpnext/e_commerce/web_template/hero_slider/hero_slider.json
@@ -165,6 +165,7 @@
    "fieldname": "slide_3_content_align",
    "fieldtype": "Select",
    "label": "Content Align",
+   "options": "Left\nCentre\nRight",
    "reqd": 0
   },
   {
@@ -214,6 +215,7 @@
    "fieldname": "slide_4_content_align",
    "fieldtype": "Select",
    "label": "Content Align",
+   "options": "Left\nCentre\nRight",
    "reqd": 0
   },
   {
@@ -263,6 +265,7 @@
    "fieldname": "slide_5_content_align",
    "fieldtype": "Select",
    "label": "Content Align",
+   "options": "Left\nCentre\nRight",
    "reqd": 0
   },
   {
@@ -274,7 +277,7 @@
   }
  ],
  "idx": 2,
- "modified": "2021-02-24 15:57:05.889709",
+ "modified": "2023-05-12 15:03:57.604060",
  "modified_by": "Administrator",
  "module": "E-commerce",
  "name": "Hero Slider",
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index ad9aafe..8c671e2 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -48,7 +48,8 @@
 			return {
 				query: "erpnext.manufacturing.doctype.bom.bom.item_query",
 				filters: {
-					"item_code": doc.item
+					"include_item_in_manufacturing": 1,
+					"is_fixed_asset": 0
 				}
 			};
 		});
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index b53149a..8058a5f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -1339,8 +1339,9 @@
 		if not has_variants:
 			query_filters["has_variants"] = 0
 
-	if filters and filters.get("is_stock_item"):
-		query_filters["is_stock_item"] = 1
+	if filters:
+		for fieldname, value in filters.items():
+			query_filters[fieldname] = value
 
 	return frappe.get_list(
 		"Item",
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 01bf2e4..051b475 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -698,6 +698,45 @@
 		bom.update_cost()
 		self.assertFalse(bom.flags.cost_updated)
 
+	def test_do_not_include_manufacturing_and_fixed_items(self):
+		from erpnext.manufacturing.doctype.bom.bom import item_query
+
+		if not frappe.db.exists("Asset Category", "Computers-Test"):
+			doc = frappe.get_doc({"doctype": "Asset Category", "asset_category_name": "Computers-Test"})
+			doc.flags.ignore_mandatory = True
+			doc.insert()
+
+		for item_code, properties in {
+			"_Test RM Item 1 Do Not Include In Manufacture": {
+				"is_stock_item": 1,
+				"include_item_in_manufacturing": 0,
+			},
+			"_Test RM Item 2 Fixed Asset Item": {
+				"is_fixed_asset": 1,
+				"is_stock_item": 0,
+				"asset_category": "Computers-Test",
+			},
+			"_Test RM Item 3 Manufacture Item": {"is_stock_item": 1, "include_item_in_manufacturing": 1},
+		}.items():
+			make_item(item_code, properties)
+
+		data = item_query(
+			"Item",
+			txt="_Test RM Item",
+			searchfield="name",
+			start=0,
+			page_len=20000,
+			filters={"include_item_in_manufacturing": 1, "is_fixed_asset": 0},
+		)
+
+		items = []
+		for row in data:
+			items.append(row[0])
+
+		self.assertTrue("_Test RM Item 1 Do Not Include In Manufacture" not in items)
+		self.assertTrue("_Test RM Item 2 Fixed Asset Item" not in items)
+		self.assertTrue("_Test RM Item 3 Manufacture Item" in items)
+
 
 def get_default_bom(item_code="_Test FG Item 2"):
 	return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 03c7b01..e158df6 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -326,7 +326,7 @@
 erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
 erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
 erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
-erpnext.patches.v14_0.update_closing_balances
+erpnext.patches.v14_0.update_closing_balances #10-05-2023
 execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
 # below migration patches should always run last
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
diff --git a/erpnext/patches/v14_0/update_closing_balances.py b/erpnext/patches/v14_0/update_closing_balances.py
index f47e730..bb108ab 100644
--- a/erpnext/patches/v14_0/update_closing_balances.py
+++ b/erpnext/patches/v14_0/update_closing_balances.py
@@ -11,6 +11,8 @@
 
 
 def execute():
+	frappe.db.truncate("Account Closing Balance")
+
 	company_wise_order = {}
 	get_opening_entries = True
 	for pcv in frappe.db.get_all(
@@ -35,7 +37,20 @@
 				entry["closing_date"] = pcv_doc.posting_date
 				entry["period_closing_voucher"] = pcv_doc.name
 
-			closing_entries = pcv_doc.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
+			closing_entries = frappe.db.get_all(
+				"GL Entry",
+				filters={
+					"is_cancelled": 0,
+					"voucher_no": ["!=", pcv.name],
+					"posting_date": ["<=", pcv.posting_date],
+				},
+				fields=["*"],
+			)
+
+			for entry in closing_entries:
+				entry["closing_date"] = pcv_doc.posting_date
+				entry["period_closing_voucher"] = pcv_doc.name
+
 			make_closing_entries(gl_entries + closing_entries, voucher_name=pcv.name)
 			company_wise_order[pcv.company].append(pcv.posting_date)
 			get_opening_entries = False
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 8efc47d..fd961c4 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -92,7 +92,7 @@
 
 	_calculate_taxes_and_totals() {
 		const is_quotation = this.frm.doc.doctype == "Quotation";
-		this.frm.doc._items = is_quotation ? this.filtered_items() : this.frm.doc.items;
+		this.frm._items = is_quotation ? this.filtered_items() : this.frm.doc.items;
 
 		this.validate_conversion_rate();
 		this.calculate_item_values();
@@ -125,7 +125,7 @@
 	calculate_item_values() {
 		var me = this;
 		if (!this.discount_amount_applied) {
-			for (const item of this.frm.doc._items || []) {
+			for (const item of this.frm._items || []) {
 				frappe.model.round_floats_in(item);
 				item.net_rate = item.rate;
 				item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
@@ -209,7 +209,7 @@
 		});
 		if(has_inclusive_tax==false) return;
 
-		$.each(me.frm.doc._items || [], function(n, item) {
+		$.each(me.frm._items || [], function(n, item) {
 			var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
 			var cumulated_tax_fraction = 0.0;
 			var total_inclusive_tax_amount_per_qty = 0;
@@ -280,13 +280,13 @@
 		var me = this;
 		this.frm.doc.total_qty = this.frm.doc.total = this.frm.doc.base_total = this.frm.doc.net_total = this.frm.doc.base_net_total = 0.0;
 
-		$.each(this.frm.doc._items || [], function(i, item) {
+		$.each(this.frm._items || [], function(i, item) {
 			me.frm.doc.total += item.amount;
 			me.frm.doc.total_qty += item.qty;
 			me.frm.doc.base_total += item.base_amount;
 			me.frm.doc.net_total += item.net_amount;
 			me.frm.doc.base_net_total += item.base_net_amount;
-			});
+		});
 	}
 
 	calculate_shipping_charges() {
@@ -333,7 +333,7 @@
 			}
 		});
 
-		$.each(this.frm.doc._items || [], function(n, item) {
+		$.each(this.frm._items || [], function(n, item) {
 			var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
 			$.each(me.frm.doc["taxes"] || [], function(i, tax) {
 				// tax_amount represents the amount of tax for the current step
@@ -342,7 +342,7 @@
 				// Adjust divisional loss to the last item
 				if (tax.charge_type == "Actual") {
 					actual_tax_dict[tax.idx] -= current_tax_amount;
-					if (n == me.frm.doc._items.length - 1) {
+					if (n == me.frm._items.length - 1) {
 						current_tax_amount += actual_tax_dict[tax.idx];
 					}
 				}
@@ -379,7 +379,7 @@
 				}
 
 				// set precision in the last item iteration
-				if (n == me.frm.doc._items.length - 1) {
+				if (n == me.frm._items.length - 1) {
 					me.round_off_totals(tax);
 					me.set_in_company_currency(tax,
 						["tax_amount", "tax_amount_after_discount_amount"]);
@@ -602,7 +602,7 @@
 
 	_cleanup() {
 		this.frm.doc.base_in_words = this.frm.doc.in_words = "";
-		let items = this.frm.doc._items;
+		let items = this.frm._items;
 
 		if(items && items.length) {
 			if(!frappe.meta.get_docfield(items[0].doctype, "item_tax_amount", this.frm.doctype)) {
@@ -659,7 +659,7 @@
 			var net_total = 0;
 			// calculate item amount after Discount Amount
 			if (total_for_discount_amount) {
-				$.each(this.frm.doc._items || [], function(i, item) {
+				$.each(this.frm._items || [], function(i, item) {
 					distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
 					item.net_amount = flt(item.net_amount - distributed_amount,
 						precision("base_amount", item));
@@ -667,7 +667,7 @@
 
 					// discount amount rounding loss adjustment if no taxes
 					if ((!(me.frm.doc.taxes || []).length || total_for_discount_amount==me.frm.doc.net_total || (me.frm.doc.apply_discount_on == "Net Total"))
-							&& i == (me.frm.doc._items || []).length - 1) {
+							&& i == (me.frm._items || []).length - 1) {
 						var discount_amount_loss = flt(me.frm.doc.net_total - net_total
 							- me.frm.doc.discount_amount, precision("net_total"));
 						item.net_amount = flt(item.net_amount + discount_amount_loss,
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 125f311..353fa9b 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -797,6 +797,8 @@
 
 @frappe.whitelist()
 def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
+	from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
+
 	def set_missing_values(source, target):
 		target.run_method("set_missing_values")
 		target.run_method("set_po_nos")
@@ -811,6 +813,8 @@
 		if target.company_address:
 			target.update(get_fetch_values("Delivery Note", "company_address", target.company_address))
 
+		make_packing_list(target)
+
 	def update_item(source, target, source_parent):
 		target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate)
 		target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate)
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 65b1097..88bc4bd 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -2042,6 +2042,75 @@
 
 		self.assertEqual(mr.items[0].qty, 6)
 
+	def test_packed_items_for_partial_sales_order(self):
+		# test Update Items with product bundle
+		for product_bundle in [
+			"_Test Product Bundle Item Partial 1",
+			"_Test Product Bundle Item Partial 2",
+		]:
+			if not frappe.db.exists("Item", product_bundle):
+				bundle_item = make_item(product_bundle, {"is_stock_item": 0})
+				bundle_item.append(
+					"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
+				)
+				bundle_item.save(ignore_permissions=True)
+
+		for product_bundle in ["_Packed Item Partial 1", "_Packed Item Partial 2"]:
+			if not frappe.db.exists("Item", product_bundle):
+				make_item(product_bundle, {"is_stock_item": 1, "stock_uom": "Nos"})
+
+			make_stock_entry(item=product_bundle, target="_Test Warehouse - _TC", qty=2, rate=10)
+
+		make_product_bundle("_Test Product Bundle Item Partial 1", ["_Packed Item Partial 1"], 1)
+
+		make_product_bundle("_Test Product Bundle Item Partial 2", ["_Packed Item Partial 2"], 1)
+
+		so = make_sales_order(
+			item_code="_Test Product Bundle Item Partial 1",
+			warehouse="_Test Warehouse - _TC",
+			qty=1,
+			uom="Nos",
+			stock_uom="Nos",
+			conversion_factor=1,
+			transaction_date=nowdate(),
+			delivery_note=nowdate(),
+			do_not_submit=1,
+		)
+
+		so.append(
+			"items",
+			{
+				"item_code": "_Test Product Bundle Item Partial 2",
+				"warehouse": "_Test Warehouse - _TC",
+				"qty": 1,
+				"uom": "Nos",
+				"stock_uom": "Nos",
+				"conversion_factor": 1,
+				"delivery_note": nowdate(),
+			},
+		)
+
+		so.save()
+		so.submit()
+
+		dn = make_delivery_note(so.name)
+		dn.remove(dn.items[1])
+		dn.save()
+		dn.submit()
+
+		self.assertEqual(len(dn.items), 1)
+		self.assertEqual(len(dn.packed_items), 1)
+		self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 1")
+
+		so.load_from_db()
+
+		dn = make_delivery_note(so.name)
+		dn.save()
+
+		self.assertEqual(len(dn.items), 1)
+		self.assertEqual(len(dn.packed_items), 1)
+		self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2")
+
 
 def automatically_fetch_payment_terms(enable=1):
 	accounts_settings = frappe.get_doc("Accounts Settings")
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
index db2b5d0..8bff4d5 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
@@ -75,7 +75,16 @@
 		self.delete_custom_fields()
 
 	def delete_custom_fields(self):
-		filters = {"fieldname": self.source_fieldname}
+		filters = {
+			"fieldname": (
+				"in",
+				[
+					self.source_fieldname,
+					f"to_{self.source_fieldname}",
+					f"from_{self.source_fieldname}",
+				],
+			)
+		}
 
 		if self.document_type:
 			filters["dt"] = self.document_type
@@ -88,6 +97,8 @@
 
 	def reset_value(self):
 		if self.apply_to_all_doctypes:
+			self.type_of_transaction = ""
+
 			self.istable = 0
 			for field in ["document_type", "condition"]:
 				self.set(field, None)
@@ -111,12 +122,35 @@
 	def on_update(self):
 		self.add_custom_fields()
 
-	def add_custom_fields(self):
-		dimension_fields = [
+	@staticmethod
+	def get_insert_after_fieldname(doctype):
+		return frappe.get_all(
+			"DocField",
+			fields=["fieldname"],
+			filters={"parent": doctype},
+			order_by="idx desc",
+			limit=1,
+		)[0].fieldname
+
+	def get_dimension_fields(self, doctype=None):
+		if not doctype:
+			doctype = self.document_type
+
+		label_start_with = ""
+		if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]:
+			label_start_with = "Target"
+		elif doctype in ["Sales Invoice Item", "Delivery Note Item", "Stock Entry Detail"]:
+			label_start_with = "Source"
+
+		label = self.dimension_name
+		if label_start_with:
+			label = f"{label_start_with} {self.dimension_name}"
+
+		return [
 			dict(
 				fieldname="inventory_dimension",
 				fieldtype="Section Break",
-				insert_after="warehouse",
+				insert_after=self.get_insert_after_fieldname(doctype),
 				label="Inventory Dimension",
 				collapsible=1,
 			),
@@ -125,24 +159,37 @@
 				fieldtype="Link",
 				insert_after="inventory_dimension",
 				options=self.reference_document,
-				label=self.dimension_name,
+				label=label,
 				reqd=self.reqd,
 				mandatory_depends_on=self.mandatory_depends_on,
 			),
 		]
 
+	def add_custom_fields(self):
 		custom_fields = {}
 
+		dimension_fields = []
 		if self.apply_to_all_doctypes:
 			for doctype in get_inventory_documents():
-				if not field_exists(doctype[0], self.source_fieldname):
-					custom_fields.setdefault(doctype[0], dimension_fields)
+				if field_exists(doctype[0], self.source_fieldname):
+					continue
+
+				dimension_fields = self.get_dimension_fields(doctype[0])
+				self.add_transfer_field(doctype[0], dimension_fields)
+				custom_fields.setdefault(doctype[0], dimension_fields)
 		elif not field_exists(self.document_type, self.source_fieldname):
+			dimension_fields = self.get_dimension_fields()
+
+			self.add_transfer_field(self.document_type, dimension_fields)
 			custom_fields.setdefault(self.document_type, dimension_fields)
 
-		if not frappe.db.get_value(
-			"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
-		) and not field_exists("Stock Ledger Entry", self.target_fieldname):
+		if (
+			dimension_fields
+			and not frappe.db.get_value(
+				"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
+			)
+			and not field_exists("Stock Ledger Entry", self.target_fieldname)
+		):
 			dimension_field = dimension_fields[1]
 			dimension_field["mandatory_depends_on"] = ""
 			dimension_field["reqd"] = 0
@@ -152,6 +199,53 @@
 		if custom_fields:
 			create_custom_fields(custom_fields)
 
+	def add_transfer_field(self, doctype, dimension_fields):
+		if doctype not in [
+			"Stock Entry Detail",
+			"Sales Invoice Item",
+			"Delivery Note Item",
+			"Purchase Invoice Item",
+			"Purchase Receipt Item",
+		]:
+			return
+
+		fieldname_start_with = "to"
+		label_start_with = "Target"
+		display_depends_on = ""
+
+		if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]:
+			fieldname_start_with = "from"
+			label_start_with = "Source"
+			display_depends_on = "eval:parent.is_internal_supplier == 1"
+		elif doctype != "Stock Entry Detail":
+			display_depends_on = "eval:parent.is_internal_customer == 1"
+		elif doctype == "Stock Entry Detail":
+			display_depends_on = "eval:parent.purpose != 'Material Issue'"
+
+		fieldname = f"{fieldname_start_with}_{self.source_fieldname}"
+		label = f"{label_start_with} {self.dimension_name}"
+
+		if field_exists(doctype, fieldname):
+			return
+
+		dimension_fields.extend(
+			[
+				dict(
+					fieldname="inventory_dimension_col_break",
+					fieldtype="Column Break",
+					insert_after=self.source_fieldname,
+				),
+				dict(
+					fieldname=fieldname,
+					fieldtype="Link",
+					insert_after="inventory_dimension_col_break",
+					options=self.reference_document,
+					label=label,
+					depends_on=display_depends_on,
+				),
+			]
+		)
+
 
 def field_exists(doctype, fieldname) -> str or None:
 	return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name")
@@ -185,18 +279,19 @@
 	dimensions = get_document_wise_inventory_dimensions(doc.doctype)
 	filter_dimensions = []
 	for row in dimensions:
-		if (
-			row.type_of_transaction == "Inward"
-			if doc.docstatus == 1
-			else row.type_of_transaction != "Inward"
-		) and sl_dict.actual_qty < 0:
-			continue
-		elif (
-			row.type_of_transaction == "Outward"
-			if doc.docstatus == 1
-			else row.type_of_transaction != "Outward"
-		) and sl_dict.actual_qty > 0:
-			continue
+		if row.type_of_transaction:
+			if (
+				row.type_of_transaction == "Inward"
+				if doc.docstatus == 1
+				else row.type_of_transaction != "Inward"
+			) and sl_dict.actual_qty < 0:
+				continue
+			elif (
+				row.type_of_transaction == "Outward"
+				if doc.docstatus == 1
+				else row.type_of_transaction != "Outward"
+			) and sl_dict.actual_qty > 0:
+				continue
 
 		evals = {"doc": doc}
 		if parent_doc:
diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
index 28b1ed9..ae5f521 100644
--- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
@@ -12,6 +12,7 @@
 	DoNotChangeError,
 	delete_dimension,
 )
+from erpnext.stock.doctype.item.test_item import create_item
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
@@ -20,6 +21,7 @@
 class TestInventoryDimension(FrappeTestCase):
 	def setUp(self):
 		prepare_test_data()
+		create_store_dimension()
 
 	def test_validate_inventory_dimension(self):
 		# Can not be child doc
@@ -73,6 +75,8 @@
 		self.assertFalse(custom_field)
 
 	def test_inventory_dimension(self):
+		frappe.local.document_wise_inventory_dimensions = {}
+
 		warehouse = "Shelf Warehouse - _TC"
 		item_code = "_Test Item"
 
@@ -143,6 +147,8 @@
 		self.assertRaises(DoNotChangeError, inv_dim1.save)
 
 	def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
+		frappe.local.document_wise_inventory_dimensions = {}
+
 		inv_dimension = create_inventory_dimension(
 			reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1
 		)
@@ -250,6 +256,97 @@
 			)
 		)
 
+	def test_for_purchase_sales_and_stock_transaction(self):
+		create_inventory_dimension(
+			reference_document="Store",
+			type_of_transaction="Outward",
+			dimension_name="Store",
+			apply_to_all_doctypes=1,
+		)
+
+		item_code = "Test Inventory Dimension Item"
+		create_item(item_code)
+		warehouse = create_warehouse("Store Warehouse")
+
+		# Purchase Receipt -> Inward in Store 1
+		pr_doc = make_purchase_receipt(
+			item_code=item_code, warehouse=warehouse, qty=10, rate=100, do_not_submit=True
+		)
+
+		pr_doc.items[0].store = "Store 1"
+		pr_doc.save()
+		pr_doc.submit()
+
+		entries = get_voucher_sl_entries(pr_doc.name, ["warehouse", "store", "incoming_rate"])
+
+		self.assertEqual(entries[0].warehouse, warehouse)
+		self.assertEqual(entries[0].store, "Store 1")
+
+		# Stock Entry -> Transfer from Store 1 to Store 2
+		se_doc = make_stock_entry(
+			item_code=item_code, qty=10, from_warehouse=warehouse, to_warehouse=warehouse, do_not_save=True
+		)
+
+		se_doc.items[0].store = "Store 1"
+		se_doc.items[0].to_store = "Store 2"
+
+		se_doc.save()
+		se_doc.submit()
+
+		entries = get_voucher_sl_entries(
+			se_doc.name, ["warehouse", "store", "incoming_rate", "actual_qty"]
+		)
+
+		for entry in entries:
+			self.assertEqual(entry.warehouse, warehouse)
+			if entry.actual_qty > 0:
+				self.assertEqual(entry.store, "Store 2")
+				self.assertEqual(entry.incoming_rate, 100.0)
+			else:
+				self.assertEqual(entry.store, "Store 1")
+
+		# Delivery Note -> Outward from Store 2
+
+		dn_doc = create_delivery_note(item_code=item_code, qty=10, warehouse=warehouse, do_not_save=True)
+
+		dn_doc.items[0].store = "Store 2"
+		dn_doc.save()
+		dn_doc.submit()
+
+		entries = get_voucher_sl_entries(dn_doc.name, ["warehouse", "store", "actual_qty"])
+
+		self.assertEqual(entries[0].warehouse, warehouse)
+		self.assertEqual(entries[0].store, "Store 2")
+		self.assertEqual(entries[0].actual_qty, -10.0)
+
+
+def get_voucher_sl_entries(voucher_no, fields):
+	return frappe.get_all(
+		"Stock Ledger Entry", filters={"voucher_no": voucher_no}, fields=fields, order_by="creation"
+	)
+
+
+def create_store_dimension():
+	if not frappe.db.exists("DocType", "Store"):
+		frappe.get_doc(
+			{
+				"doctype": "DocType",
+				"name": "Store",
+				"module": "Stock",
+				"custom": 1,
+				"naming_rule": "By fieldname",
+				"autoname": "field:store_name",
+				"fields": [{"label": "Store Name", "fieldname": "store_name", "fieldtype": "Data"}],
+				"permissions": [
+					{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
+				],
+			}
+		).insert(ignore_permissions=True)
+
+	for store in ["Store 1", "Store 2"]:
+		if not frappe.db.exists("Store", store):
+			frappe.get_doc({"doctype": "Store", "store_name": store}).insert(ignore_permissions=True)
+
 
 def prepare_test_data():
 	if not frappe.db.exists("DocType", "Shelf"):
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index cc0923f..cd076d8 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -9,7 +9,17 @@
 from frappe import _
 from frappe.model.mapper import get_mapped_doc
 from frappe.query_builder.functions import Sum
-from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
+from frappe.utils import (
+	cint,
+	comma_or,
+	cstr,
+	flt,
+	format_time,
+	formatdate,
+	getdate,
+	month_diff,
+	nowdate,
+)
 
 import erpnext
 from erpnext.accounts.general_ledger import process_gl_map
@@ -151,6 +161,41 @@
 			self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
 			self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
 
+	def submit(self):
+		if self.is_enqueue_action():
+			frappe.msgprint(
+				_(
+					"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"
+				)
+			)
+			self.queue_action("submit", timeout=2000)
+		else:
+			self._submit()
+
+	def cancel(self):
+		if self.is_enqueue_action():
+			frappe.msgprint(
+				_(
+					"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage"
+				)
+			)
+			self.queue_action("cancel", timeout=2000)
+		else:
+			self._cancel()
+
+	def is_enqueue_action(self, force=False) -> bool:
+		if force:
+			return True
+
+		if frappe.flags.in_test:
+			return False
+
+		# If line items are more than 100 or record is older than 6 months
+		if len(self.items) > 100 or month_diff(nowdate(), self.posting_date) > 6:
+			return True
+
+		return False
+
 	def on_submit(self):
 		self.update_stock_ledger()
 
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index c43a1b1..de74fda 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -5,7 +5,7 @@
 import frappe
 from frappe.permissions import add_user_permission, remove_user_permission
 from frappe.tests.utils import FrappeTestCase, change_settings
-from frappe.utils import add_days, flt, nowdate, nowtime, today
+from frappe.utils import add_days, add_to_date, flt, nowdate, nowtime, today
 
 from erpnext.accounts.doctype.account.test_account import get_inventory_account
 from erpnext.stock.doctype.item.test_item import (
@@ -1707,6 +1707,36 @@
 
 		self.assertRaises(frappe.ValidationError, sr_doc.submit)
 
+	def test_enqueue_action(self):
+		frappe.flags.in_test = False
+		item_code = "Test Enqueue Item - 001"
+		create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
+
+		doc = make_stock_entry(
+			item_code=item_code,
+			posting_date=add_to_date(today(), months=-7),
+			posting_time="00:00:00",
+			purpose="Material Receipt",
+			qty=10,
+			to_warehouse="_Test Warehouse - _TC",
+			do_not_submit=True,
+		)
+
+		self.assertTrue(doc.is_enqueue_action())
+
+		doc = make_stock_entry(
+			item_code=item_code,
+			posting_date=today(),
+			posting_time="00:00:00",
+			purpose="Material Receipt",
+			qty=10,
+			to_warehouse="_Test Warehouse - _TC",
+			do_not_submit=True,
+		)
+
+		self.assertFalse(doc.is_enqueue_action())
+		frappe.flags.in_test = True
+
 
 def make_serialized_item(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index fe81a87..6b1a8ef 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -395,7 +395,8 @@
    "no_copy": 1,
    "options": "Material Request",
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "search_index": 1
   },
   {
    "fieldname": "material_request_item",
@@ -571,7 +572,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-01-03 14:51:16.575515",
+ "modified": "2023-05-09 12:41:18.210864",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry Detail",
diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
index 7c3e151..2f65eaa 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
@@ -99,7 +99,7 @@
   },
   {
    "fieldname": "serial_no",
-   "fieldtype": "Small Text",
+   "fieldtype": "Long Text",
    "label": "Serial No"
   },
   {
@@ -120,7 +120,7 @@
   },
   {
    "fieldname": "current_serial_no",
-   "fieldtype": "Small Text",
+   "fieldtype": "Long Text",
    "label": "Current Serial No",
    "no_copy": 1,
    "print_hide": 1,
@@ -189,7 +189,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2022-11-02 13:01:23.580937",
+ "modified": "2023-05-09 18:42:19.224916",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Reconciliation Item",