test: add test cases for SRE
diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
index e7b829e..f64da92 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
@@ -1,9 +1,251 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-# import frappe
+import frappe
from frappe.tests.utils import FrappeTestCase
class TestStockReservationEntry(FrappeTestCase):
- pass
+ def setUp(self) -> None:
+ self.items = create_items()
+ create_material_receipts(self.items)
+
+ def tearDown(self) -> None:
+ return super().tearDown()
+
+ def test_validate_stock_reservation_settings(self) -> None:
+ from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
+ validate_stock_reservation_settings,
+ )
+
+ voucher = frappe._dict(
+ {
+ "doctype": "Sales Order",
+ }
+ )
+
+ # Case - 1: When `Stock Reservation` is disabled in `Stock Settings`, throw `ValidationError`
+ update_stock_settings("enable_stock_reservation", 0)
+ self.assertRaises(frappe.ValidationError, validate_stock_reservation_settings, voucher)
+
+ # Case - 2: When `Voucher Type` is not allowed for `Stock Reservation`, throw `ValidationError`
+ update_stock_settings("enable_stock_reservation", 1)
+ voucher.doctype = "NOT ALLOWED"
+ self.assertRaises(frappe.ValidationError, validate_stock_reservation_settings, voucher)
+
+ # Case - 3: When `Stock Reservation` is enabled and `Voucher Type` is allowed
+ update_stock_settings("enable_stock_reservation", 1)
+ voucher.doctype = "Sales Order"
+ self.assertIsNone(validate_stock_reservation_settings(voucher), None)
+
+ def test_get_available_qty_to_reserve(self) -> None:
+ from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
+ get_available_qty_to_reserve,
+ )
+ from erpnext.stock.utils import get_stock_balance
+
+ item_code, warehouse = "SR Item 1", "_Test Warehouse - _TC"
+
+ # Case - 1: When `Reserved Qty` is `0`, Available Qty to Reserve = Actual Qty
+ cancel_all_stock_reservation_entries()
+ available_qty_to_reserve = get_available_qty_to_reserve(item_code, warehouse)
+ expected_available_qty_to_reserve = get_stock_balance(item_code, warehouse)
+
+ self.assertEqual(available_qty_to_reserve, expected_available_qty_to_reserve)
+
+ # Case - 2: When `Reserved Qty` is `> 0`, Available Qty to Reserve = Actual Qty - Reserved Qty
+ sre = make_stock_reservation_entry(
+ item_code=item_code,
+ warehouse=warehouse,
+ ignore_validate=True,
+ )
+ available_qty_to_reserve = get_available_qty_to_reserve(item_code, warehouse)
+ expected_available_qty_to_reserve = get_stock_balance(item_code, warehouse) - sre.reserved_qty
+
+ self.assertEqual(available_qty_to_reserve, expected_available_qty_to_reserve)
+
+ def test_update_status(self) -> None:
+ sre = make_stock_reservation_entry(
+ reserved_qty=30,
+ ignore_validate=True,
+ do_not_submit=True,
+ )
+
+ # Draft: When DocStatus is `0`
+ sre.load_from_db()
+ self.assertEqual(sre.status, "Draft")
+
+ # Partially Reserved: When DocStatus is `1` and `Reserved Qty` < `Voucher Qty`
+ sre.submit()
+ sre.load_from_db()
+ self.assertEqual(sre.status, "Partially Reserved")
+
+ # Reserved: When DocStatus is `1` and `Reserved Qty` = `Voucher Qty`
+ sre.reserved_qty = sre.voucher_qty
+ sre.db_update()
+ sre.update_status()
+ sre.load_from_db()
+ self.assertEqual(sre.status, "Reserved")
+
+ # Partially Delivered: When DocStatus is `1` and (0 < `Delivered Qty` < `Voucher Qty`)
+ sre.delivered_qty = 10
+ sre.db_update()
+ sre.update_status()
+ sre.load_from_db()
+ self.assertEqual(sre.status, "Partially Delivered")
+
+ # Delivered: When DocStatus is `1` and `Delivered Qty` = `Voucher Qty`
+ sre.delivered_qty = sre.voucher_qty
+ sre.db_update()
+ sre.update_status()
+ sre.load_from_db()
+ self.assertEqual(sre.status, "Delivered")
+
+ # Cancelled: When DocStatus is `2`
+ sre.cancel()
+ sre.load_from_db()
+ self.assertEqual(sre.status, "Cancelled")
+
+ def test_update_reserved_qty_in_voucher(self) -> None:
+ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+
+ item_code, warehouse = "SR Item 1", "_Test Warehouse - _TC"
+
+ # Step - 1: Enable `Stock Reservation`
+ update_stock_settings("enable_stock_reservation", 1)
+
+ # Step - 2: Create a `Sales Order`
+ so = make_sales_order(
+ item_code=item_code,
+ warehouse=warehouse,
+ qty=50,
+ rate=100,
+ do_not_submit=True,
+ )
+ so.reserve_stock = 0 # Stock Reservation Entries won't be created on submit
+ so.items[0].reserve_stock = 1
+ so.save()
+ so.submit()
+
+ # Step - 3: Create a `Stock Reservation Entry[1]` for the `Sales Order Item`
+ sre1 = make_stock_reservation_entry(
+ item_code=item_code,
+ warehouse=warehouse,
+ voucher_type="Sales Order",
+ voucher_no=so.name,
+ voucher_detail_no=so.items[0].name,
+ reserved_qty=30,
+ )
+
+ so.load_from_db()
+ sre1.load_from_db()
+ self.assertEqual(sre1.status, "Partially Reserved")
+ self.assertEqual(so.items[0].stock_reserved_qty, sre1.reserved_qty)
+
+ # Step - 4: Create a `Stock Reservation Entry[2]` for the `Sales Order Item`
+ sre2 = make_stock_reservation_entry(
+ item_code=item_code,
+ warehouse=warehouse,
+ voucher_type="Sales Order",
+ voucher_no=so.name,
+ voucher_detail_no=so.items[0].name,
+ reserved_qty=20,
+ )
+
+ so.load_from_db()
+ sre2.load_from_db()
+ self.assertEqual(sre1.status, "Partially Reserved")
+ self.assertEqual(so.items[0].stock_reserved_qty, sre1.reserved_qty + sre2.reserved_qty)
+
+ # Step - 5: Cancel `Stock Reservation Entry[1]`
+ sre1.cancel()
+ so.load_from_db()
+ sre1.load_from_db()
+ self.assertEqual(sre1.status, "Cancelled")
+ self.assertEqual(so.items[0].stock_reserved_qty, sre2.reserved_qty)
+
+ # Step - 6: Cancel `Stock Reservation Entry[2]`
+ sre2.cancel()
+ so.load_from_db()
+ sre2.load_from_db()
+ self.assertEqual(sre1.status, "Cancelled")
+ self.assertEqual(so.items[0].stock_reserved_qty, 0)
+
+
+def update_stock_settings(field: str, value: any) -> None:
+ frappe.db.set_single_value("Stock Settings", field, value)
+
+
+def create_items() -> dict:
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ items_details = {
+ # Stock Items
+ "SR Item 1": {"is_stock_item": 1, "valuation_rate": 100},
+ "SR Item 2": {"is_stock_item": 1, "valuation_rate": 200, "stock_uom": "Kg"},
+ # Batch Items
+ "SR Batch Item 1": {"is_stock_item": 1, "valuation_rate": 100},
+ "SR Batch Item 2": {"is_stock_item": 1, "valuation_rate": 200, "stock_uom": "Kg"},
+ # Serial Items
+ "SR Serial Item 1": {"is_stock_item": 1, "valuation_rate": 100},
+ "SR Serial Item 2": {"is_stock_item": 1, "valuation_rate": 200, "stock_uom": "Kg"},
+ # Batch and Serial Items
+ "SR Batch and Serial Item 1": {"is_stock_item": 1, "valuation_rate": 100},
+ "SR Batch and Serial Item 2": {"is_stock_item": 1, "valuation_rate": 200, "stock_uom": "Kg"},
+ }
+
+ items = {}
+ for item_code, properties in items_details.items():
+ items[item_code] = make_item(item_code, properties)
+
+ return items
+
+
+def create_material_receipts(
+ items: dict, warehouse: str = "_Test Warehouse - _TC", qty: float = 100
+) -> None:
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+ for item in items.values():
+ if item.is_stock_item:
+ make_stock_entry(
+ item_code=item.item_code,
+ qty=qty,
+ to_warehouse=warehouse,
+ rate=item.valuation_rate,
+ purpose="Material Receipt",
+ )
+
+
+def cancel_all_stock_reservation_entries() -> None:
+ sre_list = frappe.db.get_all("Stock Reservation Entry", filters={"docstatus": 1}, pluck="name")
+
+ for sre in sre_list:
+ frappe.get_doc("Stock Reservation Entry", sre).cancel()
+
+
+def make_stock_reservation_entry(**args):
+ doc = frappe.new_doc("Stock Reservation Entry")
+ args = frappe._dict(args)
+
+ doc.item_code = args.item_code or "SR Item 1"
+ doc.warehouse = args.warehouse or "_Test Warehouse - _TC"
+ doc.voucher_type = args.voucher_type
+ doc.voucher_no = args.voucher_no
+ doc.voucher_detail_no = args.voucher_detail_no
+ doc.available_qty = args.available_qty or 100
+ doc.voucher_qty = args.voucher_qty or 50
+ doc.stock_uom = args.stock_uom or "Nos"
+ doc.reserved_qty = args.reserved_qty or 50
+ doc.delivered_qty = args.delivered_qty or 0
+ doc.company = args.company or "_Test Company"
+
+ if args.ignore_validate:
+ doc.flags.ignore_validate = True
+
+ if not args.do_not_save:
+ doc.save()
+ if not args.do_not_submit:
+ doc.submit()
+
+ return doc