Merge pull request #39914 from ruthra-kumar/total_row_when_filtered_on_party

refactor: add total row if only one party is being filtered
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index fef3b56..5e17881 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -94,10 +94,13 @@
 			pe.append(reference)
 
 	def update_allocated_amount(self):
-		self.allocated_amount = (
+		allocated_amount = (
 			sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
 		)
-		self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.allocated_amount
+		unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - allocated_amount
+
+		self.allocated_amount = flt(allocated_amount, self.precision("allocated_amount"))
+		self.unallocated_amount = flt(unallocated_amount, self.precision("unallocated_amount"))
 
 	def before_submit(self):
 		self.allocate_payment_entries()
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index 56ae41a..a4f01fa 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -357,7 +357,13 @@
 				and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
 
 	if filters.get("warehouse"):
-		conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
+		if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
+			lft, rgt = frappe.db.get_all(
+				"Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True
+			)[0]
+			conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) "
+		else:
+			conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
 
 	if filters.get("brand"):
 		conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 34eff77..063c1e3 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -216,6 +216,18 @@
 					)
 				)
 
+			if self.get("is_return") and self.get("return_against"):
+				document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
+				frappe.msgprint(
+					_(
+						"{0} will be treated as a standalone {0}. Post creation use {1} tool to reconcile against {2}."
+					).format(
+						document_type,
+						get_link_to_form("Payment Reconciliation"),
+						get_link_to_form(self.doctype, self.get("return_against")),
+					)
+				)
+
 			pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
 			if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
 				self.set_advances()
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index 17a2b07..eac35b0 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -539,6 +539,10 @@
 	def __add_supplied_item(self, item_row, bom_item, qty):
 		bom_item.conversion_factor = item_row.conversion_factor
 		rm_obj = self.append(self.raw_material_table, bom_item)
+		if rm_obj.get("qty"):
+			# Qty field not exists
+			rm_obj.qty = 0.0
+
 		rm_obj.reference_name = item_row.name
 
 		if self.doctype == self.subcontract_data.order_doctype:
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index dcf122c..e44d484 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -41,11 +41,49 @@
 	def validate(self):
 		self.validate_dates()
 		self.validate_duplicate_items()
+		self.set_party_item_code()
 
 	def validate_dates(self):
 		if getdate(self.from_date) > getdate(self.to_date):
 			frappe.throw(_("From date cannot be greater than To date"))
 
+	def set_party_item_code(self):
+		item_ref = {}
+		if self.blanket_order_type == "Selling":
+			item_ref = self.get_customer_items_ref()
+		else:
+			item_ref = self.get_supplier_items_ref()
+
+		if not item_ref:
+			return
+
+		for row in self.items:
+			row.party_item_code = item_ref.get(row.item_code)
+
+	def get_customer_items_ref(self):
+		items = [d.item_code for d in self.items]
+
+		return frappe._dict(
+			frappe.get_all(
+				"Item Customer Detail",
+				filters={"parent": ("in", items), "customer_name": self.customer},
+				fields=["parent", "ref_code"],
+				as_list=True,
+			)
+		)
+
+	def get_supplier_items_ref(self):
+		items = [d.item_code for d in self.items]
+
+		return frappe._dict(
+			frappe.get_all(
+				"Item Supplier",
+				filters={"parent": ("in", items), "supplier": self.supplier},
+				fields=["parent", "supplier_part_no"],
+				as_list=True,
+			)
+		)
+
 	def validate_duplicate_items(self):
 		item_list = []
 		for item in self.items:
diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
index e9fc25b..3f3b6f0 100644
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
@@ -5,6 +5,7 @@
 from frappe.utils import add_months, today
 
 from erpnext import get_company_currency
+from erpnext.stock.doctype.item.test_item import make_item
 
 from .blanket_order import make_order
 
@@ -90,6 +91,30 @@
 		frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
 		po.submit()
 
+	def test_party_item_code(self):
+		item_doc = make_item("_Test Item 1 for Blanket Order")
+		item_code = item_doc.name
+
+		customer = "_Test Customer"
+		supplier = "_Test Supplier"
+
+		if not frappe.db.exists(
+			"Item Customer Detail", {"customer_name": customer, "parent": item_code}
+		):
+			item_doc.append("customer_items", {"customer_name": customer, "ref_code": "CUST-REF-1"})
+			item_doc.save()
+
+		if not frappe.db.exists("Item Supplier", {"supplier": supplier, "parent": item_code}):
+			item_doc.append("supplier_items", {"supplier": supplier, "supplier_part_no": "SUPP-PART-1"})
+			item_doc.save()
+
+		# Blanket Order for Selling
+		bo = make_blanket_order(blanket_order_type="Selling", customer=customer, item_code=item_code)
+		self.assertEqual(bo.items[0].party_item_code, "CUST-REF-1")
+
+		bo = make_blanket_order(blanket_order_type="Purchasing", supplier=supplier, item_code=item_code)
+		self.assertEqual(bo.items[0].party_item_code, "SUPP-PART-1")
+
 
 def make_blanket_order(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json
index 977ad54..aa7831f 100644
--- a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json
+++ b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2018-05-24 07:20:04.255236",
  "doctype": "DocType",
  "editable_grid": 1,
@@ -6,6 +7,7 @@
  "field_order": [
   "item_code",
   "item_name",
+  "party_item_code",
   "column_break_3",
   "qty",
   "rate",
@@ -62,10 +64,17 @@
    "fieldname": "terms_and_conditions",
    "fieldtype": "Text",
    "label": "Terms and Conditions"
+  },
+  {
+   "fieldname": "party_item_code",
+   "fieldtype": "Data",
+   "label": "Party Item Code",
+   "read_only": 1
   }
  ],
  "istable": 1,
- "modified": "2019-11-18 19:37:46.245878",
+ "links": [],
+ "modified": "2024-02-14 18:25:26.479672",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Blanket Order Item",
@@ -74,5 +83,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py
index 068c2e9..316d294 100644
--- a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py
+++ b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py
@@ -20,6 +20,7 @@
 		parent: DF.Data
 		parentfield: DF.Data
 		parenttype: DF.Data
+		party_item_code: DF.Data | None
 		qty: DF.Float
 		rate: DF.Currency
 		terms_and_conditions: DF.Text | None
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index ba53cf8..1975c34 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -234,7 +234,7 @@
 	}
 
 	set_fields_onload_for_line_item() {
-		if (this.frm.is_new && this.frm.doc?.items) {
+		if (this.frm.is_new() && this.frm.doc?.items) {
 			this.frm.doc.items.forEach(item => {
 				if (item.docstatus === 0
 					&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 8da3e8f..6753a3a 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -936,6 +936,7 @@
 		this.toggle_related_fields(this.frm.doc);
 		this.toggle_enable_bom();
 		this.show_stock_ledger();
+		this.set_fields_onload_for_line_item();
 		erpnext.utils.view_serial_batch_nos(this.frm);
 		if (this.frm.doc.docstatus===1 && erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
 			this.show_general_ledger();
@@ -944,6 +945,35 @@
 		erpnext.utils.add_item(this.frm);
 	}
 
+	serial_no(doc, cdt, cdn) {
+		var item = frappe.get_doc(cdt, cdn);
+
+		if (item?.serial_no) {
+			// Replace all occurences of comma with line feed
+			item.serial_no = item.serial_no.replace(/,/g, '\n');
+			item.conversion_factor = item.conversion_factor || 1;
+
+			let valid_serial_nos = [];
+			let serialnos = item.serial_no.split("\n");
+			for (var i = 0; i < serialnos.length; i++) {
+				if (serialnos[i] != "") {
+					valid_serial_nos.push(serialnos[i]);
+				}
+			}
+			frappe.model.set_value(item.doctype, item.name,
+				"qty", valid_serial_nos.length / item.conversion_factor);
+		}
+	}
+
+	set_fields_onload_for_line_item() {
+		if (this.frm.is_new() && this.frm.doc?.items
+			&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1) {
+			this.frm.doc.items.forEach(item => {
+				frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1);
+			})
+		}
+	}
+
 	scan_barcode() {
 		frappe.flags.dialog_set = false;
 		const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
@@ -1074,6 +1104,10 @@
 
 		if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
 		if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
+
+		if (cint(frappe.user_defaults?.use_serial_batch_fields)) {
+			frappe.model.set_value(row.doctype, row.name, "use_serial_batch_fields", 1);
+		}
 	}
 
 	from_warehouse(doc) {
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index e59f2fe..86af9e9 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -320,15 +320,45 @@
 	if items:
 		query = query.where(sle.item_code.isin(items))
 
-	for field in ["voucher_no", "batch_no", "project", "company"]:
+	for field in ["voucher_no", "project", "company"]:
 		if filters.get(field) and field not in inventory_dimension_fields:
 			query = query.where(sle[field] == filters.get(field))
 
+	if filters.get("batch_no"):
+		bundles = get_serial_and_batch_bundles(filters)
+
+		if bundles:
+			query = query.where(
+				(sle.serial_and_batch_bundle.isin(bundles)) | (sle.batch_no == filters.batch_no)
+			)
+		else:
+			query = query.where(sle.batch_no == filters.batch_no)
+
 	query = apply_warehouse_filter(query, sle, filters)
 
 	return query.run(as_dict=True)
 
 
+def get_serial_and_batch_bundles(filters):
+	SBB = frappe.qb.DocType("Serial and Batch Bundle")
+	SBE = frappe.qb.DocType("Serial and Batch Entry")
+
+	query = (
+		frappe.qb.from_(SBE)
+		.inner_join(SBB)
+		.on(SBE.parent == SBB.name)
+		.select(SBE.parent)
+		.where(
+			(SBB.docstatus == 1)
+			& (SBB.has_batch_no == 1)
+			& (SBB.voucher_no.notnull())
+			& (SBE.batch_no == filters.batch_no)
+		)
+	)
+
+	return query.run(pluck=SBE.parent)
+
+
 def get_inventory_dimension_fields():
 	return [dimension.fieldname for dimension in get_inventory_dimensions()]
 
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index dc1529b..e4c4d60 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -113,8 +113,8 @@
 				"reference_name": self.name,
 			}
 		)
-		communication.ignore_permissions = True
-		communication.ignore_mandatory = True
+		communication.flags.ignore_permissions = True
+		communication.flags.ignore_mandatory = True
 		communication.save()
 
 	@frappe.whitelist()