Merge pull request #39165 from ruthra-kumar/bug_in_payment_entry_outstanding_reference

fix: incorrect outstanding amt validation on advance as liability
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
index f240aa6..0b29769 100644
--- a/erpnext/accounts/doctype/account/account_tree.js
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -77,7 +77,7 @@
 
 						// show Dr if positive since balance is calculated as debit - credit else show Cr
 						const balance = account.balance_in_account_currency || account.balance;
-						const dr_or_cr = balance > 0 ? "Dr": "Cr";
+						const dr_or_cr = balance > 0 ? __("Dr"): __("Cr");
 						const format = (value, currency) => format_currency(Math.abs(value), currency);
 
 						if (account.balance!==undefined) {
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
index 9e67c4c..9f56455 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
@@ -74,7 +74,7 @@
 		# after all accounts are already inserted.
 		frappe.local.flags.ignore_update_nsm = True
 		_import_accounts(chart, None, None, root_account=True)
-		rebuild_tree("Account", "parent_account")
+		rebuild_tree("Account")
 		frappe.local.flags.ignore_update_nsm = False
 
 
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index caa1d85..fc9034b 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -635,9 +635,7 @@
 	return due_date
 
 
-def validate_due_date(
-	posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None
-):
+def validate_due_date(posting_date, due_date, bill_date=None, template_name=None):
 	if getdate(due_date) < getdate(posting_date):
 		frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
 	else:
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
index 9f96449..0912c72 100644
--- a/erpnext/accounts/report/utils.py
+++ b/erpnext/accounts/report/utils.py
@@ -251,6 +251,7 @@
 		)
 		.where(
 			(je.voucher_type == "Journal Entry")
+			& (je.docstatus == 1)
 			& (journal_account.party == filters.get(args.party))
 			& (journal_account.account.isin(args.party_account))
 		)
@@ -281,7 +282,9 @@
 			pe.cost_center,
 		)
 		.where(
-			(pe.party == filters.get(args.party)) & (pe[args.account_fieldname].isin(args.party_account))
+			(pe.docstatus == 1)
+			& (pe.party == filters.get(args.party))
+			& (pe[args.account_fieldname].isin(args.party_account))
 		)
 		.orderby(pe.posting_date, pe.name, order=Order.desc)
 	)
diff --git a/erpnext/assets/doctype/asset_activity/asset_activity.py b/erpnext/assets/doctype/asset_activity/asset_activity.py
index a64cb1a..7177223 100644
--- a/erpnext/assets/doctype/asset_activity/asset_activity.py
+++ b/erpnext/assets/doctype/asset_activity/asset_activity.py
@@ -3,6 +3,7 @@
 
 import frappe
 from frappe.model.document import Document
+from frappe.utils import now_datetime
 
 
 class AssetActivity(Document):
@@ -30,5 +31,6 @@
 			"asset": asset,
 			"subject": subject,
 			"user": frappe.session.user,
+			"date": now_datetime(),
 		}
 	).insert(ignore_permissions=True, ignore_links=True)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 2efb46e..b830e7d 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -452,6 +452,7 @@
 		self.update_requested_qty()
 		self.update_ordered_qty()
 		self.update_reserved_qty_for_subcontract()
+		self.update_subcontracting_order_status()
 		self.notify_update()
 		clear_doctype_notifications(self)
 
@@ -627,6 +628,17 @@
 			if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
 				make_subcontracting_order(self.name, save=True, notify=True)
 
+	def update_subcontracting_order_status(self):
+		from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
+			update_subcontracting_order_status as update_sco_status,
+		)
+
+		if self.is_subcontracted and not self.is_old_subcontracting_flow:
+			sco = frappe.db.get_value("Subcontracting Order", {"purchase_order": self.name, "docstatus": 1})
+
+			if sco:
+				update_sco_status(sco, "Closed" if self.status == "Closed" else None)
+
 
 def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
 	"""get last purchase rate for an item"""
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index de7a7f9..82b5416 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -129,6 +129,17 @@
 			if self.doctype in relevant_docs:
 				self.set_payment_schedule()
 
+	def remove_bundle_for_non_stock_invoices(self):
+		has_sabb = False
+		if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.update_stock:
+			for item in self.get("items"):
+				if item.serial_and_batch_bundle:
+					item.serial_and_batch_bundle = None
+					has_sabb = True
+
+		if has_sabb:
+			self.remove_serial_and_batch_bundle()
+
 	def ensure_supplier_is_not_blocked(self):
 		is_supplier_payment = self.doctype == "Payment Entry" and self.party_type == "Supplier"
 		is_buying_invoice = self.doctype in ["Purchase Invoice", "Purchase Order"]
@@ -156,6 +167,9 @@
 		if self.get("_action") and self._action != "update_after_submit":
 			self.set_missing_values(for_validate=True)
 
+		if self.get("_action") == "submit":
+			self.remove_bundle_for_non_stock_invoices()
+
 		self.ensure_supplier_is_not_blocked()
 
 		self.validate_date_with_fiscal_year()
@@ -561,18 +575,12 @@
 			validate_due_date(
 				self.posting_date,
 				self.due_date,
-				"Customer",
-				self.customer,
-				self.company,
 				self.payment_terms_template,
 			)
 		elif self.doctype == "Purchase Invoice":
 			validate_due_date(
 				self.bill_date or self.posting_date,
 				self.due_date,
-				"Supplier",
-				self.supplier,
-				self.company,
 				self.bill_date,
 				self.payment_terms_template,
 			)
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 919e459..22b0d08 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -432,6 +432,9 @@
 
 		items = self.get("items") + (self.get("packed_items") or [])
 		for d in items:
+			if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
+				continue
+
 			if not self.get("return_against") or (
 				get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
 			):
diff --git a/erpnext/patches/v11_0/create_department_records_for_each_company.py b/erpnext/patches/v11_0/create_department_records_for_each_company.py
index 84be2be..7a0641d 100644
--- a/erpnext/patches/v11_0/create_department_records_for_each_company.py
+++ b/erpnext/patches/v11_0/create_department_records_for_each_company.py
@@ -35,7 +35,7 @@
 			# append list of new department for each company
 			comp_dict[company.name][department.name] = copy_doc.name
 
-	rebuild_tree("Department", "parent_department")
+	rebuild_tree("Department")
 	doctypes = ["Asset", "Employee", "Payroll Entry", "Staffing Plan", "Job Opening"]
 
 	for d in doctypes:
diff --git a/erpnext/patches/v11_0/make_location_from_warehouse.py b/erpnext/patches/v11_0/make_location_from_warehouse.py
index c863bb7..8d9c8d8 100644
--- a/erpnext/patches/v11_0/make_location_from_warehouse.py
+++ b/erpnext/patches/v11_0/make_location_from_warehouse.py
@@ -27,7 +27,7 @@
 		except frappe.DuplicateEntryError:
 			continue
 
-	rebuild_tree("Location", "parent_location")
+	rebuild_tree("Location")
 
 
 def get_parent_warehouse_name(warehouse):
diff --git a/erpnext/patches/v11_0/rebuild_tree_for_company.py b/erpnext/patches/v11_0/rebuild_tree_for_company.py
index fc06c5d..a9b2344 100644
--- a/erpnext/patches/v11_0/rebuild_tree_for_company.py
+++ b/erpnext/patches/v11_0/rebuild_tree_for_company.py
@@ -4,4 +4,4 @@
 
 def execute():
 	frappe.reload_doc("setup", "doctype", "company")
-	rebuild_tree("Company", "parent_company")
+	rebuild_tree("Company")
diff --git a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py
index 96daba7..67eb915 100644
--- a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py
+++ b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py
@@ -41,4 +41,4 @@
 			}
 		).insert(ignore_permissions=True)
 
-	rebuild_tree("Supplier Group", "parent_supplier_group")
+	rebuild_tree("Supplier Group")
diff --git a/erpnext/patches/v11_0/update_department_lft_rgt.py b/erpnext/patches/v11_0/update_department_lft_rgt.py
index 778392e..380ca4d 100644
--- a/erpnext/patches/v11_0/update_department_lft_rgt.py
+++ b/erpnext/patches/v11_0/update_department_lft_rgt.py
@@ -18,4 +18,4 @@
 		)
 	)
 
-	rebuild_tree("Department", "parent_department")
+	rebuild_tree("Department")
diff --git a/erpnext/patches/v14_0/update_invoicing_period_in_subscription.py b/erpnext/patches/v14_0/update_invoicing_period_in_subscription.py
index 2879e57..b70548c 100644
--- a/erpnext/patches/v14_0/update_invoicing_period_in_subscription.py
+++ b/erpnext/patches/v14_0/update_invoicing_period_in_subscription.py
@@ -4,5 +4,5 @@
 def execute():
 	subscription = frappe.qb.DocType("Subscription")
 	frappe.qb.update(subscription).set(
-		subscription.generate_invoice_at, "Beginning of the currency subscription period"
+		subscription.generate_invoice_at, "Beginning of the current subscription period"
 	).where(subscription.generate_invoice_at_period_start == 1).run()
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 9427c38..4d8f683 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -454,7 +454,7 @@
 		item.weight_uom = '';
 		item.conversion_factor = 0;
 
-		if(['Sales Invoice'].includes(this.frm.doc.doctype)) {
+		if(['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
 			update_stock = cint(me.frm.doc.update_stock);
 			show_batch_dialog = update_stock;
 
@@ -545,7 +545,7 @@
 								},
 								() => me.toggle_conversion_factor(item),
 								() => {
-									if (show_batch_dialog)
+									if (show_batch_dialog && !frappe.flags.trigger_from_barcode_scanner)
 										return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
 											.then((r) => {
 												if (r.message &&
@@ -1239,6 +1239,20 @@
 		}
 	}
 
+	sync_bundle_data() {
+		let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
+
+		if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
+			const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
+			barcode_scanner.sync_bundle_data();
+			barcode_scanner.remove_item_from_localstorage();
+		}
+	}
+
+	before_save(doc) {
+		this.sync_bundle_data();
+	}
+
 	service_start_date(frm, cdt, cdn) {
 		var child = locals[cdt][cdn];
 
@@ -1576,6 +1590,18 @@
 		return item_list;
 	}
 
+	items_delete() {
+		this.update_localstorage_scanned_data();
+	}
+
+	update_localstorage_scanned_data() {
+		let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
+		if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
+			const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
+			barcode_scanner.update_localstorage_scanned_data();
+		}
+	}
+
 	_set_values_for_item_list(children) {
 		const items_rule_dict = {};
 
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index a1ebfe9..cf7fab8 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -7,8 +7,6 @@
 		this.scan_barcode_field = this.frm.fields_dict[this.scan_field_name];
 
 		this.barcode_field = opts.barcode_field || "barcode";
-		this.serial_no_field = opts.serial_no_field || "serial_no";
-		this.batch_no_field = opts.batch_no_field || "batch_no";
 		this.uom_field = opts.uom_field || "uom";
 		this.qty_field = opts.qty_field || "qty";
 		// field name on row which defines max quantity to be scanned e.g. picklist
@@ -84,6 +82,7 @@
 	update_table(data) {
 		return new Promise((resolve, reject) => {
 			let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
+			frappe.flags.trigger_from_barcode_scanner = true;
 
 			const {item_code, barcode, batch_no, serial_no, uom} = data;
 
@@ -106,50 +105,38 @@
 				this.frm.has_items = false;
 			}
 
-			if (this.is_duplicate_serial_no(row, serial_no)) {
+			if (serial_no && this.is_duplicate_serial_no(row, item_code, serial_no)) {
 				this.clean_up();
 				reject();
 				return;
 			}
 
 			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_serial_and_batch(row, item_code, serial_no, batch_no),
 				() => this.set_barcode(row, barcode),
 				() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
 					this.show_scan_message(row.idx, row.item_code, qty);
 				}),
 				() => this.set_barcode_uom(row, uom),
 				() => this.clean_up(),
-				() => this.revert_selector_flag(),
-				() => resolve(row)
+				() => resolve(row),
+				() => {
+					if (row.serial_and_batch_bundle && !this.frm.is_new()) {
+						this.frm.save();
+					}
+
+					frappe.flags.trigger_from_barcode_scanner = false;
+				}
 			]);
 		});
 	}
 
-	// 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 {has_batch_no, has_serial_no} = data;
-
-		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;
-		}
-	}
-
-	revert_selector_flag() {
-		frappe.flags.hide_serial_batch_dialog = false;
-	}
-
 	set_item(row, item_code, barcode, batch_no, serial_no) {
 		return new Promise(resolve => {
 			const increment = async (value = 1) => {
 				const item_data = {item_code: item_code};
 				item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
+				frappe.flags.trigger_from_barcode_scanner = true;
 				await frappe.model.set_value(row.doctype, row.name, item_data);
 				return value;
 			};
@@ -158,8 +145,6 @@
 				frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
 					increment(value).then((value) => resolve(value));
 				});
-			} else if (this.frm.has_items) {
-				this.prepare_item_for_scan(row, item_code, barcode, batch_no, serial_no);
 			} else {
 				increment().then((value) => resolve(value));
 			}
@@ -182,9 +167,8 @@
 			frappe.model.set_value(row.doctype, row.name, item_data);
 
 			frappe.run_serially([
-				() => this.set_batch_no(row, this.dialog.get_value("batch_no")),
 				() => this.set_barcode(row, this.dialog.get_value("barcode")),
-				() => this.set_serial_no(row, this.dialog.get_value("serial_no")),
+				() => this.set_serial_and_batch(row, item_code, this.dialog.get_value("serial_no"), this.dialog.get_value("batch_no")),
 				() => this.add_child_for_remaining_qty(row),
 				() => this.clean_up()
 			]);
@@ -338,32 +322,144 @@
 		}
 	}
 
-	async set_serial_no(row, serial_no) {
-		if (serial_no && frappe.meta.has_field(row.doctype, this.serial_no_field)) {
-			const existing_serial_nos = row[this.serial_no_field];
-			let new_serial_nos = "";
-
-			if (!!existing_serial_nos) {
-				new_serial_nos = existing_serial_nos + "\n" + serial_no;
-			} else {
-				new_serial_nos = serial_no;
-			}
-			await frappe.model.set_value(row.doctype, row.name, this.serial_no_field, new_serial_nos);
+	async set_serial_and_batch(row, item_code, serial_no, batch_no) {
+		if (this.frm.is_new() || !row.serial_and_batch_bundle) {
+			this.set_bundle_in_localstorage(row, item_code, serial_no, batch_no);
+		} else if(row.serial_and_batch_bundle) {
+			frappe.call({
+				method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.update_serial_or_batch",
+				args: {
+					bundle_id: row.serial_and_batch_bundle,
+					serial_no: serial_no,
+					batch_no: batch_no,
+				},
+			})
 		}
 	}
 
+	get_key_for_localstorage() {
+		let parts = this.frm.doc.name.split("-");
+		return parts[parts.length - 1] + this.frm.doc.doctype;
+	}
+
+	update_localstorage_scanned_data() {
+		let docname = this.frm.doc.name
+		if (localStorage[docname]) {
+			let items = JSON.parse(localStorage[docname]);
+			let existing_items = this.frm.doc.items.map(d => d.item_code);
+			if (!existing_items.length) {
+				localStorage.removeItem(docname);
+				return;
+			}
+
+			for (let item_code in items) {
+				if (!existing_items.includes(item_code)) {
+					delete items[item_code];
+				}
+			}
+
+			localStorage[docname] = JSON.stringify(items);
+		}
+	}
+
+	async set_bundle_in_localstorage(row, item_code, serial_no, batch_no) {
+		let docname = this.frm.doc.name
+
+		let entries = JSON.parse(localStorage.getItem(docname));
+		if (!entries) {
+			entries = {};
+		}
+
+		let key = item_code;
+		if (!entries[key]) {
+			entries[key] = [];
+		}
+
+		let existing_row = [];
+		if (!serial_no && batch_no) {
+			existing_row = entries[key].filter((e) => e.batch_no === batch_no);
+			if (existing_row.length) {
+				existing_row[0].qty += 1;
+			}
+		} else if (serial_no) {
+			existing_row = entries[key].filter((e) => e.serial_no === serial_no);
+			if (existing_row.length) {
+				frappe.throw(__("Serial No {0} has already scanned.", [serial_no]));
+			}
+		}
+
+		if (!existing_row.length) {
+			entries[key].push({
+				"serial_no": serial_no,
+				"batch_no": batch_no,
+				"qty": 1
+			});
+		}
+
+		localStorage.setItem(docname, JSON.stringify(entries));
+
+		// Auto remove from localstorage after 1 hour
+		setTimeout(() => {
+			localStorage.removeItem(docname);
+		}, 3600000)
+	}
+
+	remove_item_from_localstorage() {
+		let docname = this.frm.doc.name;
+		if (localStorage[docname]) {
+			localStorage.removeItem(docname);
+		}
+	}
+
+	async sync_bundle_data() {
+		let docname = this.frm.doc.name;
+
+		if (localStorage[docname]) {
+			let entries = JSON.parse(localStorage[docname]);
+			if (entries) {
+				for (let entry in entries) {
+					let row = this.frm.doc.items.filter((item) => {
+						if (item.item_code === entry) {
+							return true;
+						}
+					})[0];
+
+					if (row) {
+						this.create_serial_and_batch_bundle(row, entries, entry)
+							.then(() => {
+								if (!entries) {
+									localStorage.removeItem(docname);
+								}
+							});
+					}
+				}
+			}
+		}
+	}
+
+	async create_serial_and_batch_bundle(row, entries, key) {
+		frappe.call({
+			method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers",
+			args: {
+				entries: entries[key],
+				child_row: row,
+				doc: this.frm.doc,
+				warehouse: row.warehouse,
+				do_not_save: 1
+			},
+			callback: function(r) {
+				row.serial_and_batch_bundle = r.message.name;
+				delete entries[key];
+			}
+		})
+	}
+
 	async set_barcode_uom(row, uom) {
 		if (uom && frappe.meta.has_field(row.doctype, this.uom_field)) {
 			await frappe.model.set_value(row.doctype, row.name, this.uom_field, uom);
 		}
 	}
 
-	async set_batch_no(row, batch_no) {
-		if (batch_no && frappe.meta.has_field(row.doctype, this.batch_no_field)) {
-			await frappe.model.set_value(row.doctype, row.name, this.batch_no_field, batch_no);
-		}
-	}
-
 	async set_barcode(row, barcode) {
 		if (barcode && frappe.meta.has_field(row.doctype, this.barcode_field)) {
 			await frappe.model.set_value(row.doctype, row.name, this.barcode_field, barcode);
@@ -379,13 +475,52 @@
 		}
 	}
 
-	is_duplicate_serial_no(row, serial_no) {
-		const is_duplicate = row[this.serial_no_field]?.includes(serial_no);
+	is_duplicate_serial_no(row, item_code, serial_no) {
+		if (this.frm.is_new() || !row.serial_and_batch_bundle) {
+			let is_duplicate = this.check_duplicate_serial_no_in_localstorage(item_code, serial_no);
+			if (is_duplicate) {
+				this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
+			}
 
-		if (is_duplicate) {
-			this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
+			return is_duplicate;
+		} else if (row.serial_and_batch_bundle) {
+			this.check_duplicate_serial_no_in_db(row, serial_no, (r) => {
+				if (r.message) {
+					this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
+				}
+
+				return r.message;
+			})
 		}
-		return is_duplicate;
+	}
+
+	async check_duplicate_serial_no_in_db(row, serial_no, response) {
+		frappe.call({
+			method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_duplicate_serial_no",
+			args: {
+				serial_no: serial_no,
+				bundle_id: row.serial_and_batch_bundle
+			},
+			callback(r) {
+				response(r);
+			}
+		})
+	}
+
+	check_duplicate_serial_no_in_localstorage(item_code, serial_no) {
+		let docname = this.frm.doc.name
+		let entries = JSON.parse(localStorage.getItem(docname));
+
+		if (!entries) {
+			return false;
+		}
+
+		let existing_row = [];
+		if (entries[item_code]) {
+			existing_row = entries[item_code].filter((e) => e.serial_no === serial_no);
+		}
+
+		return existing_row.length;
 	}
 
 	get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py
index 4bc98b9..df2c49b 100644
--- a/erpnext/setup/demo.py
+++ b/erpnext/setup/demo.py
@@ -149,6 +149,11 @@
 			invoice.set_posting_time = 1
 			invoice.posting_date = order.transaction_date
 			invoice.due_date = order.transaction_date
+			invoice.bill_date = order.transaction_date
+
+			if invoice.get("payment_schedule"):
+				invoice.payment_schedule[0].due_date = order.transaction_date
+
 			invoice.update_stock = 1
 			invoice.submit()
 
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 9897847..ec953b8 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -249,7 +249,7 @@
 		if frappe.flags.parent_company_changed:
 			from frappe.utils.nestedset import rebuild_tree
 
-			rebuild_tree("Company", "parent_company")
+			rebuild_tree("Company")
 
 		frappe.clear_cache()
 
@@ -397,7 +397,7 @@
 		frappe.local.flags.ignore_update_nsm = True
 		make_records(records)
 		frappe.local.flags.ignore_update_nsm = False
-		rebuild_tree("Department", "parent_department")
+		rebuild_tree("Department")
 
 	def validate_coa_input(self):
 		if self.create_chart_of_accounts_based_on == "Existing Company":
diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json
index 1143ccb..daf2df5 100644
--- a/erpnext/setup/doctype/employee/employee.json
+++ b/erpnext/setup/doctype/employee/employee.json
@@ -616,8 +616,8 @@
    "fieldname": "relieving_date",
    "fieldtype": "Date",
    "label": "Relieving Date",
-   "no_copy": 1,
    "mandatory_depends_on": "eval:doc.status == \"Left\"",
+   "no_copy": 1,
    "oldfieldname": "relieving_date",
    "oldfieldtype": "Date"
   },
@@ -822,12 +822,14 @@
  "icon": "fa fa-user",
  "idx": 24,
  "image_field": "image",
+ "is_tree": 1,
  "links": [],
- "modified": "2023-10-04 10:57:05.174592",
+ "modified": "2024-01-03 17:36:20.984421",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Employee",
  "naming_rule": "By \"Naming Series\" field",
+ "nsm_parent_field": "reports_to",
  "owner": "Administrator",
  "permissions": [
   {
@@ -860,7 +862,6 @@
    "read": 1,
    "report": 1,
    "role": "HR Manager",
-   "set_user_permissions": 1,
    "share": 1,
    "write": 1
   }
@@ -871,4 +872,4 @@
  "sort_order": "DESC",
  "states": [],
  "title_field": "employee_name"
-}
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/item_group/test_item_group.py b/erpnext/setup/doctype/item_group/test_item_group.py
index 11bc9b9..d95199d 100644
--- a/erpnext/setup/doctype/item_group/test_item_group.py
+++ b/erpnext/setup/doctype/item_group/test_item_group.py
@@ -79,7 +79,7 @@
 		group_b.save()
 
 	def test_rebuild_tree(self):
-		rebuild_tree("Item Group", "parent_item_group")
+		rebuild_tree("Item Group")
 		self.test_basic_tree()
 
 	def move_it_back(self):
diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py
index 45bf012..bd16d69 100644
--- a/erpnext/stock/__init__.py
+++ b/erpnext/stock/__init__.py
@@ -59,7 +59,7 @@
 			else:
 				from frappe.utils.nestedset import rebuild_tree
 
-				rebuild_tree("Warehouse", "parent_warehouse")
+				rebuild_tree("Warehouse")
 		else:
 			account = frappe.db.sql(
 				"""
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 3abd1d9..dae4289 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1518,6 +1518,25 @@
 			"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
 		)
 
+	def test_internal_transfer_for_non_stock_item(self):
+		from erpnext.selling.doctype.customer.test_customer import create_internal_customer
+		from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
+
+		item = make_item(properties={"is_stock_item": 0}).name
+		warehouse = "_Test Warehouse - _TC"
+		target = "Stores - _TC"
+		company = "_Test Company"
+		customer = create_internal_customer(represents_company=company)
+		rate = 100
+
+		so = make_sales_order(item_code=item, qty=1, rate=rate, customer=customer, warehouse=warehouse)
+		dn = make_delivery_note(so.name)
+		dn.items[0].target_warehouse = target
+		dn.save().submit()
+
+		self.assertEqual(so.items[0].rate, rate)
+		self.assertEqual(dn.items[0].rate, so.items[0].rate)
+
 
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
index 60624d4..d5eef5a 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
@@ -305,7 +305,7 @@
 	dimensions = get_document_wise_inventory_dimensions(doc.doctype)
 	filter_dimensions = []
 	for row in dimensions:
-		if row.type_of_transaction:
+		if row.type_of_transaction and row.type_of_transaction != "Both":
 			if (
 				row.type_of_transaction == "Inward"
 				if doc.docstatus == 1
diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
index 33394e5..361c2f8 100644
--- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
@@ -429,6 +429,14 @@
 		)
 
 		warehouse = create_warehouse("Negative Stock Warehouse")
+
+		doc = make_stock_entry(item_code=item_code, source=warehouse, qty=10, do_not_submit=True)
+		doc.items[0].inv_site = "Site 1"
+		self.assertRaises(frappe.ValidationError, doc.submit)
+		doc.reload()
+		if doc.docstatus == 1:
+			doc.cancel()
+
 		doc = make_stock_entry(item_code=item_code, target=warehouse, qty=10, do_not_submit=True)
 
 		doc.items[0].to_inv_site = "Site 1"
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 218406f..eede928 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
@@ -729,19 +729,13 @@
 
 	def before_cancel(self):
 		self.delink_serial_and_batch_bundle()
-		self.clear_table()
 
 	def delink_serial_and_batch_bundle(self):
-		self.voucher_no = None
-
 		sles = frappe.get_all("Stock Ledger Entry", filters={"serial_and_batch_bundle": self.name})
 
 		for sle in sles:
 			frappe.db.set_value("Stock Ledger Entry", sle.name, "serial_and_batch_bundle", None)
 
-	def clear_table(self):
-		self.set("entries", [])
-
 	@property
 	def child_table(self):
 		if self.voucher_type == "Job Card":
@@ -876,7 +870,6 @@
 		self.validate_voucher_no_docstatus()
 		self.delink_refernce_from_voucher()
 		self.delink_reference_from_batch()
-		self.clear_table()
 
 	@frappe.whitelist()
 	def add_serial_batch(self, data):
@@ -1156,7 +1149,7 @@
 
 
 @frappe.whitelist()
-def add_serial_batch_ledgers(entries, child_row, doc, warehouse) -> object:
+def add_serial_batch_ledgers(entries, child_row, doc, warehouse, do_not_save=False) -> object:
 	if isinstance(child_row, str):
 		child_row = frappe._dict(parse_json(child_row))
 
@@ -1170,20 +1163,23 @@
 	if frappe.db.exists("Serial and Batch Bundle", child_row.serial_and_batch_bundle):
 		sb_doc = update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse)
 	else:
-		sb_doc = create_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse)
+		sb_doc = create_serial_batch_no_ledgers(
+			entries, child_row, parent_doc, warehouse, do_not_save=do_not_save
+		)
 
 	return sb_doc
 
 
-def create_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=None) -> object:
+def create_serial_batch_no_ledgers(
+	entries, child_row, parent_doc, warehouse=None, do_not_save=False
+) -> object:
 
 	warehouse = warehouse or (
 		child_row.rejected_warehouse if child_row.is_rejected else child_row.warehouse
 	)
 
-	type_of_transaction = child_row.type_of_transaction
+	type_of_transaction = get_type_of_transaction(parent_doc, child_row)
 	if parent_doc.get("doctype") == "Stock Entry":
-		type_of_transaction = "Outward" if child_row.s_warehouse else "Inward"
 		warehouse = warehouse or child_row.s_warehouse or child_row.t_warehouse
 
 	doc = frappe.get_doc(
@@ -1214,13 +1210,30 @@
 
 	doc.save()
 
-	frappe.db.set_value(child_row.doctype, child_row.name, "serial_and_batch_bundle", doc.name)
+	if do_not_save:
+		frappe.db.set_value(child_row.doctype, child_row.name, "serial_and_batch_bundle", doc.name)
 
 	frappe.msgprint(_("Serial and Batch Bundle created"), alert=True)
 
 	return doc
 
 
+def get_type_of_transaction(parent_doc, child_row):
+	type_of_transaction = child_row.type_of_transaction
+	if parent_doc.get("doctype") == "Stock Entry":
+		type_of_transaction = "Outward" if child_row.s_warehouse else "Inward"
+
+	if not type_of_transaction:
+		type_of_transaction = "Outward"
+		if parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"]:
+			type_of_transaction = "Inward"
+
+	if parent_doc.get("is_return"):
+		type_of_transaction = "Inward" if type_of_transaction == "Outward" else "Outward"
+
+	return type_of_transaction
+
+
 def update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=None) -> object:
 	doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
 	doc.voucher_detail_no = child_row.name
@@ -1247,6 +1260,25 @@
 	return doc
 
 
+@frappe.whitelist()
+def update_serial_or_batch(bundle_id, serial_no=None, batch_no=None):
+	if batch_no and not serial_no:
+		if qty := frappe.db.get_value(
+			"Serial and Batch Entry", {"parent": bundle_id, "batch_no": batch_no}, "qty"
+		):
+			frappe.db.set_value(
+				"Serial and Batch Entry", {"parent": bundle_id, "batch_no": batch_no}, "qty", qty + 1
+			)
+			return
+
+	doc = frappe.get_cached_doc("Serial and Batch Bundle", bundle_id)
+	if not serial_no and not batch_no:
+		return
+
+	doc.append("entries", {"serial_no": serial_no, "batch_no": batch_no, "qty": 1})
+	doc.save(ignore_permissions=True)
+
+
 def get_serial_and_batch_ledger(**kwargs):
 	kwargs = frappe._dict(kwargs)
 
@@ -2032,3 +2064,8 @@
 @frappe.whitelist()
 def get_batch_no_from_serial_no(serial_no):
 	return frappe.get_cached_value("Serial No", serial_no, "batch_no")
+
+
+@frappe.whitelist()
+def is_duplicate_serial_no(bundle_id, serial_no):
+	return frappe.db.exists("Serial and Batch Entry", {"parent": bundle_id, "serial_no": serial_no})
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 2ccee94..bccbc28 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -24,6 +24,7 @@
 
 import erpnext
 from erpnext.accounts.general_ledger import process_gl_map
+from erpnext.buying.utils import check_on_hold_or_closed_status
 from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
 from erpnext.manufacturing.doctype.bom.bom import (
 	add_additional_cost,
@@ -208,7 +209,6 @@
 		self.validate_bom()
 		self.set_process_loss_qty()
 		self.validate_purchase_order()
-		self.validate_subcontracting_order()
 
 		if self.purpose in ("Manufacture", "Repack"):
 			self.mark_finished_and_scrap_items()
@@ -274,6 +274,7 @@
 		return False
 
 	def on_submit(self):
+		self.validate_closed_subcontracting_order()
 		self.update_stock_ledger()
 		self.update_work_order()
 		self.validate_subcontract_order()
@@ -294,6 +295,7 @@
 			self.set_material_request_transfer_status("Completed")
 
 	def on_cancel(self):
+		self.validate_closed_subcontracting_order()
 		self.update_subcontract_order_supplied_items()
 		self.update_subcontracting_order_status()
 
@@ -1203,19 +1205,9 @@
 					)
 				)
 
-	def validate_subcontracting_order(self):
-		if self.get("subcontracting_order") and self.purpose in [
-			"Send to Subcontractor",
-			"Material Transfer",
-		]:
-			sco_status = frappe.db.get_value("Subcontracting Order", self.subcontracting_order, "status")
-
-			if sco_status == "Closed":
-				frappe.throw(
-					_("Cannot create Stock Entry against a closed Subcontracting Order {0}.").format(
-						self.subcontracting_order
-					)
-				)
+	def validate_closed_subcontracting_order(self):
+		if self.get("subcontracting_order"):
+			check_on_hold_or_closed_status("Subcontracting Order", self.subcontracting_order)
 
 	def mark_finished_and_scrap_items(self):
 		if self.purpose != "Repack" and any(
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 6e7af68..277ca01 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -111,16 +111,20 @@
 				"posting_date": self.posting_date,
 				"posting_time": self.posting_time,
 				"company": self.company,
+				"sle": self.name,
 			}
 		)
 
 		sle = get_previous_sle(kwargs, extra_cond=extra_cond)
+		qty_after_transaction = 0.0
+		flt_precision = cint(frappe.db.get_default("float_precision")) or 2
 		if sle:
-			flt_precision = cint(frappe.db.get_default("float_precision")) or 2
-			diff = sle.qty_after_transaction + flt(self.actual_qty)
-			diff = flt(diff, flt_precision)
-			if diff < 0 and abs(diff) > 0.0001:
-				self.throw_validation_error(diff, dimensions)
+			qty_after_transaction = sle.qty_after_transaction
+
+		diff = qty_after_transaction + flt(self.actual_qty)
+		diff = flt(diff, flt_precision)
+		if diff < 0 and abs(diff) > 0.0001:
+			self.throw_validation_error(diff, dimensions)
 
 	def throw_validation_error(self, diff, dimensions):
 		dimension_msg = _(", with the inventory {0}: {1}").format(
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 39df227..4cfe5d8 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -209,7 +209,7 @@
 		frappe.db.set_value(
 			"Serial and Batch Bundle",
 			{"voucher_no": self.sle.voucher_no, "voucher_type": self.sle.voucher_type},
-			{"is_cancelled": 1, "voucher_no": ""},
+			{"is_cancelled": 1},
 		)
 
 		if self.sle.serial_and_batch_bundle:
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index bd0d469..4b0e284 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -591,6 +591,13 @@
 		as_dict=True,
 	)
 	if batch_no_data:
+		if frappe.get_cached_value("Item", batch_no_data.item_code, "has_serial_no"):
+			frappe.throw(
+				_(
+					"Batch No {0} is linked with Item {1} which has serial no. Please scan serial no instead."
+				).format(search_value, batch_no_data.item_code)
+			)
+
 		_update_item_info(batch_no_data)
 		set_cache(batch_no_data)
 		return batch_no_data
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
index 587a3b4..4c8a0ad 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
@@ -101,9 +101,32 @@
 	},
 
 	refresh: function (frm) {
+		if (frm.doc.docstatus == 1 && frm.has_perm("submit")) {
+			if (frm.doc.status == "Closed") {
+				frm.add_custom_button(__('Re-open'), () => frm.events.update_subcontracting_order_status(frm), __("Status"));
+			} else if(flt(frm.doc.per_received, 2) < 100) {
+				frm.add_custom_button(__('Close'), () => frm.events.update_subcontracting_order_status(frm, "Closed"), __("Status"));
+			}
+		}
+
 		frm.trigger('get_materials_from_supplier');
 	},
 
+	update_subcontracting_order_status(frm, status) {
+		frappe.call({
+			method: "erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.update_subcontracting_order_status",
+			args: {
+				sco: frm.doc.name,
+				status: status,
+			},
+			callback: function (r) {
+				if (!r.exc) {
+					frm.reload_doc();
+				}
+			},
+		});
+	},
+
 	get_materials_from_supplier: function (frm) {
 		let sco_rm_details = [];
 
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json
index 28c52c9..507e233 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json
@@ -370,7 +370,7 @@
    "in_standard_filter": 1,
    "label": "Status",
    "no_copy": 1,
-   "options": "Draft\nOpen\nPartially Received\nCompleted\nMaterial Transferred\nPartial Material Transferred\nCancelled",
+   "options": "Draft\nOpen\nPartially Received\nCompleted\nMaterial Transferred\nPartial Material Transferred\nCancelled\nClosed",
    "print_hide": 1,
    "read_only": 1,
    "reqd": 1,
@@ -454,7 +454,7 @@
  "icon": "fa fa-file-text",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-06-03 16:18:17.782538",
+ "modified": "2024-01-03 20:56:04.670380",
  "modified_by": "Administrator",
  "module": "Subcontracting",
  "name": "Subcontracting Order",
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
index 0fe8c13..daccbbb 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
@@ -7,7 +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.buying.utils import check_on_hold_or_closed_status
 from erpnext.controllers.subcontracting_controller import SubcontractingController
 from erpnext.stock.stock_balance import update_bin_qty
 from erpnext.stock.utils import get_bin
@@ -68,6 +68,7 @@
 			"Material Transferred",
 			"Partial Material Transferred",
 			"Cancelled",
+			"Closed",
 		]
 		supplied_items: DF.Table[SubcontractingOrderSuppliedItem]
 		supplier: DF.Link
@@ -112,16 +113,10 @@
 
 	def on_submit(self):
 		self.update_prevdoc_status()
-		self.update_requested_qty()
-		self.update_ordered_qty_for_subcontracting()
-		self.update_reserved_qty_for_subcontracting()
 		self.update_status()
 
 	def on_cancel(self):
 		self.update_prevdoc_status()
-		self.update_requested_qty()
-		self.update_ordered_qty_for_subcontracting()
-		self.update_reserved_qty_for_subcontracting()
 		self.update_status()
 
 	def validate_purchase_order_for_subcontracting(self):
@@ -277,6 +272,9 @@
 		self.set_missing_values()
 
 	def update_status(self, status=None, update_modified=True):
+		if self.status == "Closed" and self.status != status:
+			check_on_hold_or_closed_status("Purchase Order", self.purchase_order)
+
 		if self.docstatus >= 1 and not status:
 			if self.docstatus == 1:
 				if self.status == "Draft":
@@ -285,11 +283,6 @@
 					status = "Completed"
 				elif self.per_received > 0 and self.per_received < 100:
 					status = "Partially Received"
-					for item in self.supplied_items:
-						if not item.returned_qty or (item.supplied_qty - item.consumed_qty - item.returned_qty) > 0:
-							break
-					else:
-						status = "Closed"
 				else:
 					total_required_qty = total_supplied_qty = 0
 					for item in self.supplied_items:
@@ -304,13 +297,12 @@
 			elif self.docstatus == 2:
 				status = "Cancelled"
 
-		if status:
-			frappe.db.set_value(
-				"Subcontracting Order", self.name, "status", status, update_modified=update_modified
-			)
+		if status and self.status != status:
+			self.db_set("status", status, update_modified=update_modified)
 
-			if status == "Closed":
-				update_po_status("Closed", self.purchase_order)
+		self.update_requested_qty()
+		self.update_ordered_qty_for_subcontracting()
+		self.update_reserved_qty_for_subcontracting()
 
 
 @frappe.whitelist()
@@ -357,8 +349,8 @@
 
 
 @frappe.whitelist()
-def update_subcontracting_order_status(sco):
+def update_subcontracting_order_status(sco, status=None):
 	if isinstance(sco, str):
 		sco = frappe.get_doc("Subcontracting Order", sco)
 
-	sco.update_status()
+	sco.update_status(status)
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
index 7ca1264..ec54944 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
@@ -10,7 +10,7 @@
 			"Completed": "green",
 			"Partial Material Transferred": "purple",
 			"Material Transferred": "blue",
-			"Closed": "red",
+			"Closed": "green",
 			"Cancelled": "red",
 		};
 		return [__(doc.status), status_colors[doc.status], "status,=," + doc.status];
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
index 37dabf1..6c0ee45 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
@@ -95,14 +95,14 @@
 		self.assertEqual(sco.status, "Partially Received")
 
 		# Closed
-		ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items])
-		ste.save()
-		ste.submit()
-		sco.load_from_db()
+		sco.update_status("Closed")
 		self.assertEqual(sco.status, "Closed")
-		ste.cancel()
-		sco.load_from_db()
+		scr = make_subcontracting_receipt(sco.name)
+		scr.save()
+		self.assertRaises(frappe.exceptions.ValidationError, scr.submit)
+		sco.update_status()
 		self.assertEqual(sco.status, "Partially Received")
+		scr.cancel()
 
 		# Completed
 		scr = make_subcontracting_receipt(sco.name)
@@ -564,7 +564,6 @@
 
 		sco.load_from_db()
 
-		self.assertEqual(sco.status, "Closed")
 		self.assertEqual(sco.supplied_items[0].returned_qty, 5)
 
 	def test_ordered_qty_for_subcontracting_order(self):
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
index 575c4ed..0535799 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
@@ -93,7 +93,8 @@
 					get_query_filters: {
 						docstatus: 1,
 						per_received: ['<', 100],
-						company: frm.doc.company
+						company: frm.doc.company,
+						status: ['!=', 'Closed'],
 					}
 				});
 			}, __('Get Items From'));
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 52bf13c..7c2a1f1 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -8,6 +8,7 @@
 
 import erpnext
 from erpnext.accounts.utils import get_account_currency
+from erpnext.buying.utils import check_on_hold_or_closed_status
 from erpnext.controllers.subcontracting_controller import SubcontractingController
 from erpnext.stock.stock_ledger import get_valuation_rate
 
@@ -142,6 +143,7 @@
 		self.get_current_stock()
 
 	def on_submit(self):
+		self.validate_closed_subcontracting_order()
 		self.validate_available_qty_for_consumption()
 		self.update_status_updater_args()
 		self.update_prevdoc_status()
@@ -165,6 +167,7 @@
 			"Repost Item Valuation",
 			"Serial and Batch Bundle",
 		)
+		self.validate_closed_subcontracting_order()
 		self.update_status_updater_args()
 		self.update_prevdoc_status()
 		self.set_consumed_qty_in_subcontract_order()
@@ -175,6 +178,11 @@
 		self.update_status()
 		self.delete_auto_created_batches()
 
+	def validate_closed_subcontracting_order(self):
+		for item in self.items:
+			if item.subcontracting_order:
+				check_on_hold_or_closed_status("Subcontracting Order", item.subcontracting_order)
+
 	def validate_items_qty(self):
 		for item in self.items:
 			if not (item.qty or item.rejected_qty):
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index d05d0d9..84c71ba 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -600,7 +600,7 @@
 Course Enrollment {0} does not exists,Die Kursanmeldung {0} existiert nicht,
 Course Schedule,Kurstermine,
 Course: ,Kurs:,
-Cr,Haben,
+Cr,H,
 Create,Erstellen,
 Create BOM,Stückliste anlegen,
 Create Delivery Trip,Erstelle Auslieferungsfahrt,
@@ -3401,7 +3401,7 @@
 Doctype,DocType,
 Document {0} successfully uncleared,Dokument {0} wurde nicht erfolgreich gelöscht,
 Download Template,Vorlage herunterladen,
-Dr,Soll,
+Dr,S,
 Due Date,Fälligkeitsdatum,
 Duplicate,Duplizieren,
 Duplicate Project with Tasks,Projekt mit Aufgaben duplizieren,
@@ -7370,6 +7370,7 @@
 Sample Retention Warehouse,Beispiel Retention Warehouse,
 Default Valuation Method,Standard-Bewertungsmethode,
 Show Barcode Field,Anzeigen Barcode-Feld,
+Show Balances in Chart Of Accounts,Saldo in Kontenplan anzeigen,
 Convert Item Description to Clean HTML,Elementbeschreibung in HTML bereinigen,
 Allow Negative Stock,Negativen Lagerbestand zulassen,
 Automatically Set Serial Nos based on FIFO,Automatisch Seriennummern auf Basis FIFO einstellen,
@@ -8812,7 +8813,6 @@
 Field Mapping,Feldzuordnung,
 Not Specified,Keine Angabe,
 Update Type,Aktualisierungsart,
-Dr,Soll,
 End Time,Endzeit,
 Fetching...,Abrufen ...,
 "It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.","Es scheint, dass ein Problem mit der Stripe-Konfiguration des Servers vorliegt. Im Falle eines Fehlers wird der Betrag Ihrem Konto gutgeschrieben.",
diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py
index 679d5bd..9678488 100644
--- a/erpnext/utilities/bulk_transaction.py
+++ b/erpnext/utilities/bulk_transaction.py
@@ -15,18 +15,15 @@
 
 	length_of_data = len(deserialized_data)
 
-	if length_of_data > 10:
-		frappe.msgprint(
-			_("Started a background job to create {1} {0}").format(to_doctype, length_of_data)
-		)
-		frappe.enqueue(
-			job,
-			deserialized_data=deserialized_data,
-			from_doctype=from_doctype,
-			to_doctype=to_doctype,
-		)
-	else:
-		job(deserialized_data, from_doctype, to_doctype)
+	frappe.msgprint(
+		_("Started a background job to create {1} {0}").format(to_doctype, length_of_data)
+	)
+	frappe.enqueue(
+		job,
+		deserialized_data=deserialized_data,
+		from_doctype=from_doctype,
+		to_doctype=to_doctype,
+	)
 
 
 @frappe.whitelist()