diff --git a/erpnext/stock/report/reserved_stock/__init__.py b/erpnext/stock/report/reserved_stock/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.js b/erpnext/stock/report/reserved_stock/reserved_stock.js
new file mode 100644
index 000000000000..e60c3b61dcf1
--- /dev/null
+++ b/erpnext/stock/report/reserved_stock/reserved_stock.js
@@ -0,0 +1,167 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.query_reports["Reserved Stock"] = {
+ filters: [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ reqd: 1,
+ default: frappe.defaults.get_user_default("Company"),
+ },
+ {
+ fieldname: "from_date",
+ label: __("From Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(
+ frappe.datetime.get_today(),
+ -1
+ ),
+ reqd: 1,
+ },
+ {
+ fieldname: "to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.get_today(),
+ reqd: 1,
+ },
+ {
+ fieldname: "item_code",
+ label: __("Item"),
+ fieldtype: "Link",
+ options: "Item",
+ get_query: () => ({
+ filters: {
+ is_stock_item: 1,
+ },
+ }),
+ },
+ {
+ fieldname: "warehouse",
+ label: __("Warehouse"),
+ fieldtype: "Link",
+ options: "Warehouse",
+ get_query: () => ({
+ filters: {
+ is_group: 0,
+ company: frappe.query_report.get_filter_value("company"),
+ },
+ }),
+ },
+ {
+ fieldname: "stock_reservation_entry",
+ label: __("Stock Reservation Entry"),
+ fieldtype: "Link",
+ options: "Stock Reservation Entry",
+ get_query: () => ({
+ filters: {
+ docstatus: 1,
+ company: frappe.query_report.get_filter_value("company"),
+ },
+ }),
+ },
+ {
+ fieldname: "voucher_type",
+ label: __("Voucher Type"),
+ fieldtype: "Link",
+ options: "DocType",
+ default: "Sales Order",
+ get_query: () => ({
+ filters: {
+ name: ["in", ["Sales Order"]],
+ }
+ }),
+ },
+ {
+ fieldname: "voucher_no",
+ label: __("Voucher No"),
+ fieldtype: "Dynamic Link",
+ options: "voucher_type",
+ get_query: () => ({
+ filters: {
+ docstatus: 1,
+ company: frappe.query_report.get_filter_value("company"),
+ },
+ }),
+ },
+ {
+ fieldname: "against_pick_list",
+ label: __("Against Pick List"),
+ fieldtype: "Link",
+ options: "Pick List",
+ get_query: () => ({
+ filters: {
+ docstatus: 1,
+ company: frappe.query_report.get_filter_value("company"),
+ },
+ }),
+ },
+ {
+ fieldname: "reservation_based_on",
+ label: __("Reservation Based On"),
+ fieldtype: "Select",
+ options: ["", "Qty", "Serial and Batch"],
+ },
+ {
+ fieldname: "status",
+ label: __("Status"),
+ fieldtype: "Select",
+ options: [
+ "",
+ "Partially Reserved",
+ "Reserved",
+ "Partially Delivered",
+ "Delivered",
+ ],
+ },
+ {
+ fieldname: "project",
+ label: __("Project"),
+ fieldtype: "Link",
+ options: "Project",
+ get_query: () => ({
+ filters: {
+ company: frappe.query_report.get_filter_value("company"),
+ },
+ }),
+ },
+ ],
+ formatter: (value, row, column, data, default_formatter) => {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "status") {
+ switch (data.status) {
+ case "Partially Reserved":
+ value = "" + value + "";
+ break;
+ case "Reserved":
+ value = "" + value + "";
+ break;
+ case "Partially Delivered":
+ value = "" + value + "";
+ break;
+ case "Delivered":
+ value = "" + value + "";
+ break;
+ }
+ }
+ else if (column.fieldname == "delivered_qty") {
+ if (data.delivered_qty > 0) {
+ if (data.reserved_qty > data.delivered_qty) {
+ value = "" + value + "";
+ }
+ else {
+ value = "" + value + "";
+ }
+ }
+ else {
+ value = "" + value + "";
+ }
+ }
+
+ return value;
+ },
+};
diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.json b/erpnext/stock/report/reserved_stock/reserved_stock.json
new file mode 100644
index 000000000000..17b916afda80
--- /dev/null
+++ b/erpnext/stock/report/reserved_stock/reserved_stock.json
@@ -0,0 +1,26 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2023-08-02 22:11:19.439620",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letterhead": null,
+ "modified": "2023-08-03 12:46:33.780222",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Reserved Stock",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Reservation Entry",
+ "report_name": "Reserved Stock",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "System Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.py b/erpnext/stock/report/reserved_stock/reserved_stock.py
new file mode 100644
index 000000000000..72f7623b556b
--- /dev/null
+++ b/erpnext/stock/report/reserved_stock/reserved_stock.py
@@ -0,0 +1,192 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.query_builder.functions import Date
+
+
+def execute(filters=None):
+ columns, data = [], []
+
+ validate_filters(filters)
+
+ columns = get_columns()
+ data = get_data(filters)
+
+ return columns, data
+
+
+def validate_filters(filters):
+ if not filters:
+ frappe.throw(_("Please set filters"))
+
+ for field in ["company", "from_date", "to_date"]:
+ if not filters.get(field):
+ frappe.throw(_("Please set {0}").format(field))
+
+ if filters.get("from_date") > filters.get("to_date"):
+ frappe.throw(_("From Date cannot be greater than To Date"))
+
+
+def get_data(filters):
+ sre = frappe.qb.DocType("Stock Reservation Entry")
+ query = (
+ frappe.qb.from_(sre)
+ .select(
+ sre.creation,
+ sre.warehouse,
+ sre.item_code,
+ sre.stock_uom,
+ sre.voucher_qty,
+ sre.reserved_qty,
+ sre.delivered_qty,
+ (sre.available_qty - sre.reserved_qty).as_("available_qty"),
+ sre.voucher_type,
+ sre.voucher_no,
+ sre.against_pick_list,
+ sre.name.as_("stock_reservation_entry"),
+ sre.status,
+ sre.project,
+ sre.company,
+ )
+ .where(
+ (sre.docstatus == 1)
+ & (sre.company == filters.get("company"))
+ & (
+ (Date(sre.creation) >= filters.get("from_date"))
+ & (Date(sre.creation) <= filters.get("to_date"))
+ )
+ )
+ )
+
+ for field in [
+ "company",
+ "item_code",
+ "warehouse",
+ "voucher_type",
+ "voucher_no",
+ "against_pick_list",
+ "reservation_based_on",
+ "status",
+ "project",
+ ]:
+ if value := filters.get(field):
+ query = query.where((sre[field] == value))
+
+ if value := filters.get("stock_reservation_entry"):
+ query = query.where((sre.name == value))
+
+ data = query.run(as_list=True)
+
+ return data
+
+
+def get_columns():
+ columns = [
+ {
+ "label": _("Date"),
+ "fieldname": "date",
+ "fieldtype": "Datetime",
+ "width": 150,
+ },
+ {
+ "fieldname": "warehouse",
+ "label": _("Warehouse"),
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 150,
+ },
+ {
+ "fieldname": "item_code",
+ "label": _("Item"),
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100,
+ },
+ {
+ "fieldname": "stock_uom",
+ "label": _("Stock UOM"),
+ "fieldtype": "Link",
+ "options": "UOM",
+ "width": 100,
+ },
+ {
+ "fieldname": "voucher_qty",
+ "label": _("Voucher Qty"),
+ "fieldtype": "Float",
+ "width": 110,
+ "convertible": "qty",
+ },
+ {
+ "fieldname": "reserved_qty",
+ "label": _("Reserved Qty"),
+ "fieldtype": "Float",
+ "width": 110,
+ "convertible": "qty",
+ },
+ {
+ "fieldname": "delivered_qty",
+ "label": _("Delivered Qty"),
+ "fieldtype": "Float",
+ "width": 110,
+ "convertible": "qty",
+ },
+ {
+ "fieldname": "available_qty",
+ "label": _("Available Qty to Reserve"),
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty",
+ },
+ {
+ "fieldname": "voucher_type",
+ "label": _("Voucher Type"),
+ "fieldtype": "Data",
+ "options": "Warehouse",
+ "width": 110,
+ },
+ {
+ "fieldname": "voucher_no",
+ "label": _("Voucher No"),
+ "fieldtype": "Dynamic Link",
+ "options": "voucher_type",
+ "width": 120,
+ },
+ {
+ "fieldname": "against_pick_list",
+ "label": _("Against Pick List"),
+ "fieldtype": "Link",
+ "options": "Pick List",
+ "width": 130,
+ },
+ {
+ "fieldname": "stock_reservation_entry",
+ "label": _("Stock Reservation Entry"),
+ "fieldtype": "Link",
+ "options": "Stock Reservation Entry",
+ "width": 150,
+ },
+ {
+ "fieldname": "status",
+ "label": _("Status"),
+ "fieldtype": "Data",
+ "width": 120,
+ },
+ {
+ "fieldname": "project",
+ "label": _("Project"),
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 100,
+ },
+ {
+ "fieldname": "company",
+ "label": _("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 110,
+ },
+ ]
+
+ return columns
diff --git a/erpnext/stock/report/reserved_stock/test_reserved_stock.py b/erpnext/stock/report/reserved_stock/test_reserved_stock.py
new file mode 100644
index 000000000000..f408c0f860ef
--- /dev/null
+++ b/erpnext/stock/report/reserved_stock/test_reserved_stock.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from random import randint
+
+import frappe
+from frappe.tests.utils import FrappeTestCase, change_settings
+
+from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+from erpnext.stock.doctype.stock_reservation_entry.test_stock_reservation_entry import (
+ cancel_all_stock_reservation_entries,
+ create_items,
+ create_material_receipt,
+)
+from erpnext.stock.report.reserved_stock.reserved_stock import get_data as reserved_stock_report
+
+
+class TestReservedStock(FrappeTestCase):
+ def setUp(self) -> None:
+ super().setUp()
+ self.stock_qty = 100
+ self.warehouse = "_Test Warehouse - _TC"
+
+ def tearDown(self) -> None:
+ cancel_all_stock_reservation_entries()
+ return super().tearDown()
+
+ @change_settings(
+ "Stock Settings",
+ {
+ "allow_negative_stock": 0,
+ "enable_stock_reservation": 1,
+ "auto_reserve_serial_and_batch": 1,
+ "pick_serial_and_batch_based_on": "FIFO",
+ },
+ )
+ def test_reserved_stock_report(self):
+ items_details = create_items()
+ create_material_receipt(items_details, self.warehouse, qty=self.stock_qty)
+
+ for item_code, properties in items_details.items():
+ so = make_sales_order(
+ item_code=item_code, qty=randint(11, 100), warehouse=self.warehouse, uom=properties.stock_uom
+ )
+ so.create_stock_reservation_entries()
+
+ data = reserved_stock_report(filters={"warehouse": self.warehouse})
+ self.assertEqual(len(data), len(items_details))
+
+ for d in data:
+ self.assertEqual(d.voucher_qty, d.reserved_qty)
+ self.assertEqual(d.available_qty, self.stock_qty - d.reserved_qty)