From a918adaa33eccaf16a8a6fca46b7e36428287d9c Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 9 Apr 2023 08:28:10 +0530 Subject: [PATCH] test: add test cases for SRE --- .../test_stock_reservation_entry.py | 246 +++++++++++++++++- 1 file changed, 244 insertions(+), 2 deletions(-) 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 e7b829e7c1..f64da92a85 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