diff --git a/erpnext/stock/report/stock_qty_vs_batch_qty/__init__.py b/erpnext/stock/report/stock_qty_vs_batch_qty/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.js b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.js
new file mode 100644
index 00000000000..f80126bcb0a
--- /dev/null
+++ b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.js
@@ -0,0 +1,71 @@
+// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.query_reports["Stock Qty vs Batch Qty"] = {
+ filters: [
+ {
+ fieldname: "item",
+ label: __("Item"),
+ fieldtype: "Link",
+ options: "Item",
+ get_query: function () {
+ return {
+ filters: { has_batch_no: true },
+ };
+ },
+ },
+ {
+ fieldname: "batch",
+ label: __("Batch"),
+ fieldtype: "Link",
+ options: "Batch",
+ get_query: function () {
+ const item_code = frappe.query_report.get_filter_value("item");
+ return {
+ filters: { item: item_code, disabled: 0 },
+ };
+ },
+ },
+ ],
+ onload: function (report) {
+ report.page.add_inner_button(__("Update Batch Qty"), function () {
+ let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
+ let selected_rows = indexes
+ .map((i) => frappe.query_report.data[i])
+ .filter((row) => row.difference != 0);
+
+ if (selected_rows.length) {
+ frappe.call({
+ method: "erpnext.stock.report.stock_qty_vs_batch_qty.stock_qty_vs_batch_qty.update_batch_qty",
+ args: {
+ selected_batches: selected_rows,
+ },
+ callback: function (r) {
+ if (!r.exc) {
+ report.refresh();
+ }
+ },
+ });
+ } else {
+ frappe.msgprint(__("Please select at least one row with difference value"));
+ }
+ });
+ },
+
+ formatter: function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+ if (column.fieldname == "difference" && data) {
+ if (data.difference > 0) {
+ value = "" + value + "";
+ } else if (data.difference < 0) {
+ value = "" + value + "";
+ }
+ }
+ return value;
+ },
+ get_datatable_options(options) {
+ return Object.assign(options, {
+ checkboxColumn: true,
+ });
+ },
+};
diff --git a/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.json b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.json
new file mode 100644
index 00000000000..147815be88d
--- /dev/null
+++ b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.json
@@ -0,0 +1,28 @@
+{
+ "add_total_row": 0,
+ "add_translate_data": 0,
+ "columns": [],
+ "creation": "2025-10-07 20:03:45.952352",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letterhead": null,
+ "modified": "2025-11-18 11:35:04.615085",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock Qty vs Batch Qty",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Item",
+ "report_name": "Stock Qty vs Batch Qty",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Item Manager"
+ }
+ ],
+ "timeout": 0
+}
diff --git a/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.py b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.py
new file mode 100644
index 00000000000..d88d610d23e
--- /dev/null
+++ b/erpnext/stock/report/stock_qty_vs_batch_qty/stock_qty_vs_batch_qty.py
@@ -0,0 +1,120 @@
+# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import json
+
+import frappe
+from frappe import _
+
+from erpnext.stock.doctype.batch.batch import get_batch_qty
+
+
+def execute(filters=None):
+ if not filters:
+ filters = {}
+
+ columns = get_columns()
+ data = get_data(filters)
+
+ return columns, data
+
+
+def get_columns() -> list[dict]:
+ columns = [
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 200,
+ },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 200},
+ {"label": _("Batch"), "fieldname": "batch", "fieldtype": "Link", "options": "Batch", "width": 200},
+ {"label": _("Batch Qty"), "fieldname": "batch_qty", "fieldtype": "Float", "width": 150},
+ {"label": _("Stock Qty"), "fieldname": "stock_qty", "fieldtype": "Float", "width": 150},
+ {"label": _("Difference"), "fieldname": "difference", "fieldtype": "Float", "width": 150},
+ ]
+
+ return columns
+
+
+def get_data(filters=None):
+ filters = filters or {}
+
+ item = filters.get("item")
+ batch_no = filters.get("batch")
+
+ batch_sle_data = (
+ get_batch_qty(
+ item_code=item, batch_no=batch_no, for_stock_levels=True, consider_negative_batches=True
+ )
+ or []
+ )
+
+ stock_qty_map = {}
+ for row in batch_sle_data:
+ batch = row.get("batch_no")
+ if not batch:
+ continue
+ stock_qty_map[batch] = stock_qty_map.get(batch, 0) + (row.get("qty") or 0)
+
+ batch = frappe.qb.DocType("Batch")
+
+ query = (
+ frappe.qb.from_(batch)
+ .select(batch.name, batch.item, batch.item_name, batch.batch_qty)
+ .where(batch.disabled == 0)
+ )
+
+ if item:
+ query = query.where(batch.item == item)
+ if batch_no:
+ query = query.where(batch.name == batch_no)
+
+ batch_records = query.run(as_dict=True) or []
+
+ result = []
+ for row in batch_records:
+ name = row.get("name")
+ batch_qty = row.get("batch_qty") or 0
+ stock_qty = stock_qty_map.get(name, 0)
+ difference = stock_qty - batch_qty
+
+ if difference != 0:
+ result.append(
+ {
+ "item_code": row.get("item"),
+ "item_name": row.get("item_name"),
+ "batch": name,
+ "batch_qty": batch_qty,
+ "stock_qty": stock_qty,
+ "difference": difference,
+ }
+ )
+
+ return result
+
+
+@frappe.whitelist()
+def update_batch_qty(selected_batches=None):
+ if not selected_batches:
+ return
+
+ selected_batches = json.loads(selected_batches)
+ for row in selected_batches:
+ batch_name = row.get("batch")
+
+ batches = get_batch_qty(
+ batch_no=batch_name,
+ item_code=row.get("item_code"),
+ for_stock_levels=True,
+ consider_negative_batches=True,
+ )
+ batch_qty = 0.0
+ if batches:
+ for batch in batches:
+ batch_qty += batch.get("qty")
+
+ frappe.db.set_value("Batch", batch_name, "batch_qty", batch_qty)
+
+ frappe.msgprint(_("Batch Qty updated successfully"), alert=True)