Merge pull request #40372 from ruthra-kumar/toggle_for_standalone_cr_dr_notes

refactor: checkbox to toggle standalone Credit/Debit note behaviour
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
index 9e6b51d..65158fc 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
@@ -9,7 +9,6 @@
 from frappe.model.document import Document
 from frappe.query_builder.custom import ConstantColumn
 from frappe.utils import cint, flt
-from pypika.terms import Parameter
 
 from erpnext import get_default_cost_center
 from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
@@ -509,6 +508,18 @@
 	to_reference_date,
 ):
 	exact_match = True if "exact_match" in document_types else False
+
+	common_filters = frappe._dict(
+		{
+			"amount": transaction.unallocated_amount,
+			"payment_type": "Receive" if transaction.deposit > 0.0 else "Pay",
+			"reference_no": transaction.reference_number,
+			"party_type": transaction.party_type,
+			"party": transaction.party,
+			"bank_account": bank_account,
+		}
+	)
+
 	queries = get_queries(
 		bank_account,
 		company,
@@ -520,20 +531,12 @@
 		from_reference_date,
 		to_reference_date,
 		exact_match,
+		common_filters,
 	)
 
-	filters = {
-		"amount": transaction.unallocated_amount,
-		"payment_type": "Receive" if transaction.deposit > 0.0 else "Pay",
-		"reference_no": transaction.reference_number,
-		"party_type": transaction.party_type,
-		"party": transaction.party,
-		"bank_account": bank_account,
-	}
-
 	matching_vouchers = []
 	for query in queries:
-		matching_vouchers.extend(frappe.db.sql(query, filters, as_dict=True))
+		matching_vouchers.extend(query.run(as_dict=True))
 
 	return (
 		sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
@@ -551,6 +554,7 @@
 	from_reference_date,
 	to_reference_date,
 	exact_match,
+	common_filters,
 ):
 	# get queries to get matching vouchers
 	account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from"
@@ -571,6 +575,7 @@
 				filter_by_reference_date,
 				from_reference_date,
 				to_reference_date,
+				common_filters,
 			)
 			or []
 		)
@@ -590,6 +595,7 @@
 	filter_by_reference_date,
 	from_reference_date,
 	to_reference_date,
+	common_filters,
 ):
 	queries = []
 	currency = get_account_currency(bank_account)
@@ -604,6 +610,7 @@
 			filter_by_reference_date,
 			from_reference_date,
 			to_reference_date,
+			common_filters,
 		)
 		queries.append(query)
 
@@ -616,16 +623,17 @@
 			filter_by_reference_date,
 			from_reference_date,
 			to_reference_date,
+			common_filters,
 		)
 		queries.append(query)
 
 	if transaction.deposit > 0.0 and "sales_invoice" in document_types:
-		query = get_si_matching_query(exact_match, currency)
+		query = get_si_matching_query(exact_match, currency, common_filters)
 		queries.append(query)
 
 	if transaction.withdrawal > 0.0:
 		if "purchase_invoice" in document_types:
-			query = get_pi_matching_query(exact_match, currency)
+			query = get_pi_matching_query(exact_match, currency, common_filters)
 			queries.append(query)
 
 	if "bank_transaction" in document_types:
@@ -680,7 +688,7 @@
 		.where(amount_condition)
 		.where(bt.docstatus == 1)
 	)
-	return str(query)
+	return query
 
 
 def get_pe_matching_query(
@@ -692,6 +700,7 @@
 	filter_by_reference_date,
 	from_reference_date,
 	to_reference_date,
+	common_filters,
 ):
 	# get matching payment entries query
 	to_from = "to" if transaction.deposit > 0.0 else "from"
@@ -734,7 +743,7 @@
 		.where(pe.docstatus == 1)
 		.where(pe.payment_type.isin([payment_type, "Internal Transfer"]))
 		.where(pe.clearance_date.isnull())
-		.where(getattr(pe, account_from_to) == Parameter("%(bank_account)s"))
+		.where(getattr(pe, account_from_to) == common_filters.bank_account)
 		.where(amount_condition)
 		.where(filter_by_date)
 		.orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date)
@@ -743,7 +752,7 @@
 	if frappe.flags.auto_reconcile_vouchers == True:
 		query = query.where(ref_condition)
 
-	return str(query)
+	return query
 
 
 def get_je_matching_query(
@@ -754,6 +763,7 @@
 	filter_by_reference_date,
 	from_reference_date,
 	to_reference_date,
+	common_filters,
 ):
 	# get matching journal entry query
 	# We have mapping at the bank level
@@ -793,7 +803,7 @@
 		.where(je.docstatus == 1)
 		.where(je.voucher_type != "Opening Entry")
 		.where(je.clearance_date.isnull())
-		.where(jea.account == Parameter("%(bank_account)s"))
+		.where(jea.account == common_filters.bank_account)
 		.where(amount_equality if exact_match else getattr(jea, amount_field) > 0.0)
 		.where(je.docstatus == 1)
 		.where(filter_by_date)
@@ -803,19 +813,19 @@
 	if frappe.flags.auto_reconcile_vouchers == True:
 		query = query.where(ref_condition)
 
-	return str(query)
+	return query
 
 
-def get_si_matching_query(exact_match, currency):
+def get_si_matching_query(exact_match, currency, common_filters):
 	# get matching sales invoice query
 	si = frappe.qb.DocType("Sales Invoice")
 	sip = frappe.qb.DocType("Sales Invoice Payment")
 
-	amount_equality = sip.amount == Parameter("%(amount)s")
+	amount_equality = sip.amount == common_filters.amount
 	amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
 	amount_condition = amount_equality if exact_match else sip.amount > 0.0
 
-	party_condition = si.customer == Parameter("%(party)s")
+	party_condition = si.customer == common_filters.party
 	party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
 
 	query = (
@@ -836,23 +846,23 @@
 		)
 		.where(si.docstatus == 1)
 		.where(sip.clearance_date.isnull())
-		.where(sip.account == Parameter("%(bank_account)s"))
+		.where(sip.account == common_filters.bank_account)
 		.where(amount_condition)
 		.where(si.currency == currency)
 	)
 
-	return str(query)
+	return query
 
 
-def get_pi_matching_query(exact_match, currency):
+def get_pi_matching_query(exact_match, currency, common_filters):
 	# get matching purchase invoice query when they are also used as payment entries (is_paid)
 	purchase_invoice = frappe.qb.DocType("Purchase Invoice")
 
-	amount_equality = purchase_invoice.paid_amount == Parameter("%(amount)s")
+	amount_equality = purchase_invoice.paid_amount == common_filters.amount
 	amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
 	amount_condition = amount_equality if exact_match else purchase_invoice.paid_amount > 0.0
 
-	party_condition = purchase_invoice.supplier == Parameter("%(party)s")
+	party_condition = purchase_invoice.supplier == common_filters.party
 	party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
 
 	query = (
@@ -872,9 +882,9 @@
 		.where(purchase_invoice.docstatus == 1)
 		.where(purchase_invoice.is_paid == 1)
 		.where(purchase_invoice.clearance_date.isnull())
-		.where(purchase_invoice.cash_bank_account == Parameter("%(bank_account)s"))
+		.where(purchase_invoice.cash_bank_account == common_filters.bank_account)
 		.where(amount_condition)
 		.where(purchase_invoice.currency == currency)
 	)
 
-	return str(query)
+	return query
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7970a3e..95bb188 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -30,7 +30,7 @@
 	make_reverse_gl_entries,
 	process_gl_map,
 )
-from erpnext.accounts.party import get_party_account
+from erpnext.accounts.party import get_party_account, set_contact_details
 from erpnext.accounts.utils import (
 	cancel_exchange_gain_loss_journal,
 	get_account_currency,
@@ -444,6 +444,8 @@
 				self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
 
 		if self.party:
+			if not self.contact_person:
+				set_contact_details(self, party=frappe._dict({"name": self.party}), party_type=self.party_type)
 			if not self.party_balance:
 				self.party_balance = get_balance_on(
 					party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
index 0bb8d3a..a9c1900 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -60,6 +60,8 @@
 	"free_item_rate",
 	"same_item",
 	"is_recursive",
+	"recurse_for",
+	"apply_recursion_over",
 	"apply_multiple_pricing_rules",
 ]
 
diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
index 9e576fb..9b40c98 100644
--- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
@@ -107,6 +107,28 @@
 		price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
 		self.assertEqual(price_rules, [])
 
+	def test_pricing_rule_for_product_discount_slabs(self):
+		ps = make_promotional_scheme()
+		ps.set("price_discount_slabs", [])
+		ps.set(
+			"product_discount_slabs",
+			[
+				{
+					"rule_description": "12+1",
+					"min_qty": 12,
+					"free_item": "_Test Item 2",
+					"free_qty": 1,
+					"is_recursive": 1,
+					"recurse_for": 12,
+				}
+			],
+		)
+		ps.save()
+		pr = frappe.get_doc("Pricing Rule", {"promotional_scheme_id": ps.product_discount_slabs[0].name})
+		self.assertSequenceEqual(
+			[pr.min_qty, pr.free_item, pr.free_qty, pr.recurse_for], [12, "_Test Item 2", 1, 12]
+		)
+
 
 def make_promotional_scheme(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
index 3eab515..4e61d04 100644
--- a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
+++ b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
@@ -27,7 +27,9 @@
   "threshold_percentage",
   "column_break_15",
   "priority",
-  "is_recursive"
+  "is_recursive",
+  "recurse_for",
+  "apply_recursion_over"
  ],
  "fields": [
   {
@@ -161,17 +163,36 @@
    "fieldname": "is_recursive",
    "fieldtype": "Check",
    "label": "Is Recursive"
+  },
+  {
+   "default": "0",
+   "depends_on": "is_recursive",
+   "description": "Give free item for every N quantity",
+   "fieldname": "recurse_for",
+   "fieldtype": "Float",
+   "label": "Recurse Every (As Per Transaction UOM)",
+   "mandatory_depends_on": "is_recursive"
+  },
+  {
+   "default": "0",
+   "depends_on": "is_recursive",
+   "description": "Qty for which recursion isn't applicable.",
+   "fieldname": "apply_recursion_over",
+   "fieldtype": "Float",
+   "label": "Apply Recursion Over (As Per Transaction UOM)",
+   "mandatory_depends_on": "is_recursive"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-03-06 21:58:18.162346",
+ "modified": "2024-03-12 12:53:58.199108",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Promotional Scheme Product Discount",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py
index 7dd5fea..1463a7b 100644
--- a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py
+++ b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py
@@ -15,6 +15,7 @@
 		from frappe.types import DF
 
 		apply_multiple_pricing_rules: DF.Check
+		apply_recursion_over: DF.Float
 		disable: DF.Check
 		free_item: DF.Link | None
 		free_item_rate: DF.Currency
@@ -51,6 +52,7 @@
 			"19",
 			"20",
 		]
+		recurse_for: DF.Float
 		rule_description: DF.SmallText
 		same_item: DF.Check
 		threshold_percentage: DF.Percent
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 9f19366..1a79103 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -408,11 +408,11 @@
 		# Earlier subscription didn't had any company field
 		company = self.get("company") or get_default_company()
 		if not company:
-			# fmt: off
 			frappe.throw(
-				_("Company is mandatory was generating invoice. Please set default company in Global Defaults.")
+				_(
+					"Company is mandatory for generating an invoice. Please set a default company in Global Defaults."
+				)
 			)
-			# fmt: on
 
 		invoice = frappe.new_doc(self.invoice_document_type)
 		invoice.company = company
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 0d64188..bb1ed35 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -670,7 +670,7 @@
 	searchfields = frappe.get_meta(doctype).get_search_fields()
 
 	meta = frappe.get_meta(doctype)
-	if meta.is_tree:
+	if meta.is_tree and meta.has_field("is_group"):
 		query_filters.append(["is_group", "=", 0])
 
 	if meta.has_field("disabled"):
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 800e756..1ddcaa7 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -576,8 +576,52 @@
 				return
 
 			for qty_field in ["stock_qty", "rejected_qty"]:
-				if target_doc.get(qty_field):
+				if target_doc.get(qty_field) and not target_doc.get("use_serial_batch_fields"):
 					update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
+				elif target_doc.get(qty_field) and target_doc.get("use_serial_batch_fields"):
+					update_non_bundled_serial_nos(source_doc, target_doc, source_parent)
+
+	def update_non_bundled_serial_nos(source_doc, target_doc, source_parent):
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+		if source_doc.serial_no:
+			returned_serial_nos = get_returned_non_bundled_serial_nos(source_doc, source_parent)
+			serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos))
+			if serial_nos:
+				target_doc.serial_no = "\n".join(serial_nos)
+
+		if source_doc.get("rejected_serial_no"):
+			returned_serial_nos = get_returned_non_bundled_serial_nos(
+				source_doc, source_parent, serial_no_field="rejected_serial_no"
+			)
+			rejected_serial_nos = list(
+				set(get_serial_nos(source_doc.rejected_serial_no)) - set(returned_serial_nos)
+			)
+			if rejected_serial_nos:
+				target_doc.rejected_serial_no = "\n".join(rejected_serial_nos)
+
+	def get_returned_non_bundled_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+		return_ref_field = frappe.scrub(child_doc.doctype)
+		if child_doc.doctype == "Delivery Note Item":
+			return_ref_field = "dn_detail"
+
+		serial_nos = []
+
+		fields = [f"`{'tab' + child_doc.doctype}`.`{serial_no_field}`"]
+
+		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],
+		]
+
+		for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
+			serial_nos.extend(get_serial_nos(row.get(serial_no_field)))
+
+		return serial_nos
 
 	def update_terms(source_doc, target_doc, source_parent):
 		target_doc.payment_amount = -source_doc.payment_amount
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 35aebb9..e889c5d 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -264,7 +264,7 @@
 		if not self.has_overlap(production_capacity, time_logs):
 			return {}
 
-		if self.workstation_type and time_logs:
+		if not self.workstation and self.workstation_type and time_logs:
 			if workstation_time := self.get_workstation_based_on_available_slot(time_logs):
 				self.workstation = workstation_time.get("workstation")
 				return workstation_time
@@ -420,7 +420,7 @@
 		if not workstation_doc.working_hours or cint(
 			frappe.db.get_single_value("Manufacturing Settings", "allow_overtime")
 		):
-			if get_datetime(row.planned_end_time) < get_datetime(row.planned_start_time):
+			if get_datetime(row.planned_end_time) <= get_datetime(row.planned_start_time):
 				row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.time_in_mins)
 				row.remaining_time_in_mins = 0.0
 			else:
diff --git a/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html b/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
index 8824c98..69c8f44 100644
--- a/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
+++ b/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
@@ -52,10 +52,10 @@
 		</span>
 	</div>
 	<div class="col-sm-1">
-		<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add" data-item-code="{{ escape(row.item_code) }}">Add</button>
+		<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add" data-item-code="{{ escape(row.item_code) }}">{{ __("Add") }}</button>
 	</div>
 	<div class="col-sm-1">
-		<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-move" data-item-code="{{ escape(row.item_code) }}">Move</button>
+		<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-move" data-item-code="{{ escape(row.item_code) }}">{{ __("Move") }}</button>
 	</div>
 </div>
-{% }); %}
\ No newline at end of file
+{% }); %}
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 5e22707..233ca19 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -330,6 +330,13 @@
 		else:
 			status = "Cancelled"
 
+		if (
+			self.skip_transfer
+			and self.produced_qty
+			and self.qty > (flt(self.produced_qty) + flt(self.process_loss_qty))
+		):
+			status = "In Process"
+
 		return status
 
 	def update_work_order_qty(self):
@@ -784,7 +791,7 @@
 				)
 
 	def update_completed_qty_in_material_request(self):
-		if self.material_request:
+		if self.material_request and self.material_request_item:
 			frappe.get_doc("Material Request", self.material_request).update_completed_qty(
 				[self.material_request_item]
 			)
diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js
index e9c409e..3181d76 100644
--- a/erpnext/public/js/controllers/stock_controller.js
+++ b/erpnext/public/js/controllers/stock_controller.js
@@ -11,6 +11,18 @@
 		}
 	}
 
+	barcode(doc, cdt, cdn)  {
+		let row = locals[cdt][cdn];
+		if (row.barcode) {
+			erpnext.stock.utils.set_item_details_using_barcode(this.frm, row, (r) => {
+				frappe.model.set_value(cdt, cdn, {
+					"item_code": r.message.item_code,
+					"qty": 1,
+				});
+			});
+		}
+	}
+
 	setup_warehouse_query() {
 		var me = this;
 		erpnext.queries.setup_queries(this.frm, "Warehouse", function() {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index f43e3e7..8135bb2 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -411,6 +411,19 @@
 		barcode_scanner.process_scan();
 	}
 
+	barcode(doc, cdt, cdn)  {
+		let row = locals[cdt][cdn];
+		if (row.barcode) {
+			erpnext.stock.utils.set_item_details_using_barcode(this.frm, row, (r) => {
+				debugger
+				frappe.model.set_value(cdt, cdn, {
+					"item_code": r.message.item_code,
+					"qty": 1,
+				});
+			});
+		}
+	}
+
 	validate_has_items () {
 		let table = this.frm.doc.items;
 		this.frm.has_items = (table && table.length
@@ -1813,8 +1826,8 @@
 			let items = [];
 
 			me.frm.doc.items.forEach(d => {
-				// if same item was added a free item through a different pricing rule, keep it
-				if(d.item_code != item.remove_free_item || !d.is_free_item || removed_pricing_rule?.includes(d.pricing_rules)) {
+				// if same item was added as free item through a different pricing rule, keep it
+				if(d.item_code != item.remove_free_item || !d.is_free_item || !removed_pricing_rule?.includes(d.pricing_rules)) {
 					items.push(d);
 				}
 			});
@@ -2216,7 +2229,7 @@
 		});
 
 		this.frm.doc.items.forEach(item => {
-			if (!item.quality_inspection) {
+			if (this.has_inspection_required(item)) {
 				let dialog_items = dialog.fields_dict.items;
 				dialog_items.df.data.push({
 					"docname": item.name,
@@ -2240,6 +2253,16 @@
 		}
 	}
 
+	has_inspection_required(item) {
+		if (this.frm.doc.doctype === "Stock Entry" && this.frm.doc.purpose == "Manufacture" ) {
+			if (item.is_finished_item && !item.quality_inspection) {
+				return true;
+			}
+		} else if (!item.quality_inspection) {
+			return true;
+		}
+	}
+
 	get_method_for_payment() {
 		var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
 		if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index f17f60a..7655ad9 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -2,6 +2,7 @@
 // License: GNU General Public License v3. See license.txt
 frappe.provide("erpnext");
 frappe.provide("erpnext.utils");
+frappe.provide("erpnext.stock.utils");
 
 $.extend(erpnext, {
 	get_currency: function (company) {
@@ -1201,3 +1202,10 @@
 		context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true);
 	});
 }
+
+$.extend(erpnext.stock.utils, {
+	set_item_details_using_barcode(frm, child_row, callback) {
+		const barcode_scanner = new erpnext.utils.BarcodeScanner({ frm: frm });
+		barcode_scanner.scan_api_call(child_row.barcode, callback);
+	},
+});
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index b5189b8..9fcda0d 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -2101,6 +2101,46 @@
 			self.assertFalse(row.warehouse == rejected_warehouse)
 			self.assertTrue(row.warehouse == warehouse)
 
+	def test_pick_list_for_batch(self):
+		from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note
+
+		batch_item = make_item(
+			"_Test Batch Item for Pick LIST",
+			properties={
+				"has_batch_no": 1,
+				"create_new_batch": 1,
+				"batch_number_series": "BATCH-SDDTBIFRM-.#####",
+			},
+		).name
+
+		warehouse = "_Test Warehouse - _TC"
+		se = make_stock_entry(item_code=batch_item, qty=10, target=warehouse, use_serial_batch_fields=1)
+		so = make_sales_order(item_code=batch_item, qty=10, warehouse=warehouse)
+		pick_list = create_pick_list(so.name)
+
+		pick_list.save()
+		batch_no = frappe.get_all(
+			"Serial and Batch Entry",
+			filters={"parent": se.items[0].serial_and_batch_bundle},
+			fields=["batch_no"],
+		)[0].batch_no
+
+		for row in pick_list.locations:
+			self.assertEqual(row.qty, 10.0)
+			self.assertTrue(row.warehouse == warehouse)
+			self.assertTrue(row.batch_no == batch_no)
+
+		pick_list.submit()
+
+		dn = create_delivery_note(pick_list.name)
+		for row in dn.items:
+			self.assertEqual(row.qty, 10.0)
+			self.assertTrue(row.warehouse == warehouse)
+			self.assertTrue(row.batch_no == batch_no)
+
+		dn.submit()
+		dn.reload()
+
 
 def automatically_fetch_payment_terms(enable=1):
 	accounts_settings = frappe.get_doc("Accounts Settings")
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f79803c..b206406 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -2069,6 +2069,7 @@
 			as_dict=1,
 		)
 
+		precision = frappe.get_precision("Stock Entry Detail", "qty")
 		for key, row in available_materials.items():
 			remaining_qty_to_produce = flt(wo_data.trans_qty) - flt(wo_data.produced_qty)
 			if remaining_qty_to_produce <= 0 and not self.is_return:
@@ -2091,7 +2092,8 @@
 				serial_nos = row.serial_nos[0 : cint(qty)]
 				row.serial_nos = serial_nos
 
-			self.update_item_in_stock_entry_detail(row, item, qty)
+			if flt(qty, precision) != 0.0:
+				self.update_item_in_stock_entry_detail(row, item, qty)
 
 	def update_batches_to_be_consume(self, batches, row, qty):
 		qty_to_be_consumed = qty
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 06fd5f9..3356ad5 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -138,12 +138,12 @@
 						"voucher_type": self.doctype,
 						"voucher_no": self.name,
 						"voucher_detail_no": row.name,
-						"qty": row.qty,
+						"qty": row.current_qty,
 						"type_of_transaction": "Outward",
 						"company": self.company,
 						"is_rejected": 0,
 						"serial_nos": get_serial_nos(row.current_serial_no) if row.current_serial_no else None,
-						"batches": frappe._dict({row.batch_no: row.qty}) if row.batch_no else None,
+						"batches": frappe._dict({row.batch_no: row.current_qty}) if row.batch_no else None,
 						"batch_no": row.batch_no,
 						"do_not_submit": True,
 					}
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 12df0fab..4e87fa0 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -599,6 +599,7 @@
 		elif self.sle.voucher_no:
 			query = query.where(parent.voucher_no != self.sle.voucher_no)
 
+		query = query.where(parent.voucher_type != "Pick List")
 		if timestamp_condition:
 			query = query.where(timestamp_condition)